diff --git a/CMakeLists.txt b/CMakeLists.txt index c9321f0a..7fd59f31 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,7 +228,7 @@ set(MEGA_LIBTHEORA_VER "1.1.1") set(MEGA_FREETYPE_VER "2.13.2") set(MEGA_SDL2_VER "2.28.5") set(MEGA_SDL3_VER "3.2.10") -set(MEGA_OPENAL_VER "1.23.1-bc7cb17") +set(MEGA_OPENAL_VER "1.24.3") set(MEGA_MODPLUG_VER "0.8.8.4") set(SKIP_INSTALL_ALL TRUE) diff --git a/libs/openal-soft/.clang-tidy b/libs/openal-soft/.clang-tidy new file mode 100644 index 00000000..3769fb87 --- /dev/null +++ b/libs/openal-soft/.clang-tidy @@ -0,0 +1,180 @@ +--- + Checks: '-*, + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-assignment-in-if-condition, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-casting-through-void, + bugprone-chained-comparison, + bugprone-compare-pointer-to-member-virtual-function, + bugprone-copy-constructor-init, + bugprone-crtp-constructor-accessibility, + bugprone-dangling-handle, + bugprone-dynamic-static-initializers, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-forwarding-reference-overload, + bugprone-implicit-widening-of-multiplication-result, + bugprone-inaccurate-erase, + bugprone-incorrect-*, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-lambda-function-name, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-*, + bugprone-move-forwarding-reference, + bugprone-multi-level-implicit-pointer-conversion, + bugprone-multiple-*, + bugprone-narrowing-conversions, + bugprone-no-escape, + bugprone-non-zero-enum-to-bool-conversion, + bugprone-not-null-terminated-result, + bugprone-optional-value-conversion, + bugprone-parent-virtual-call, + bugprone-pointer-arithmetic-on-polymorphic-object, + bugprone-posix-return, + bugprone-redundant-branch-condition, + bugprone-reserved-identifier, + bugprone-return-const-ref-from-parameter, + bugprone-shared-ptr-array-mismatch, + bugprone-signal-handler, + bugprone-signed-char-misuse, + bugprone-sizeof-*, + bugprone-spuriously-wake-up-functions, + bugprone-standalone-empty, + bugprone-string-*, + bugprone-stringview-nullptr, + bugprone-suspicious-*, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-too-small-loop-variable, + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-*, + bugprone-unique-ptr-array-mismatch, + bugprone-unsafe-functions, + bugprone-unused-*, + bugprone-use-after-move, + bugprone-virtual-near-miss, + cert-dcl50-cpp, + cert-dcl58-cpp, + cert-env33-c, + cert-err34-c, + cert-err52-cpp, + cert-err60-cpp, + cert-flp30-c, + cert-mem57-cpp, + clang-analyzer-apiModeling.*, + clang-analyzer-core.*, + clang-analyzer-cplusplus.*, + clang-analyzer-deadcode.DeadStores, + clang-analyzer-fuchsia.HandleChecker, + clang-analyzer-nullability.*, + clang-analyzer-optin.*, + clang-analyzer-osx.*, + clang-analyzer-security.FloatLoopCounter, + clang-analyzer-security.PutenvStackArray, + clang-analyzer-security.SetgidSetuidOrder, + clang-analyzer-security.cert.env.InvalidPtr, + clang-analyzer-security.insecureAPI.SecuritySyntaxChecker, + clang-analyzer-security.insecureAPI.UncheckedReturn, + clang-analyzer-security.insecureAPI.bcmp, + clang-analyzer-security.insecureAPI.bcopy, + clang-analyzer-security.insecureAPI.bzero, + clang-analyzer-security.insecureAPI.decodeValueOfObjCType, + clang-analyzer-security.insecureAPI.getpw, + clang-analyzer-security.insecureAPI.gets, + clang-analyzer-security.insecureAPI.mkstemp, + clang-analyzer-security.insecureAPI.mktemp, + clang-analyzer-security.insecureAPI.rand, + clang-analyzer-security.insecureAPI.strcpy, + clang-analyzer-security.insecureAPI.vfork, + clang-analyzer-unix.*, + clang-analyzer-valist.*, + clang-analyzer-webkit.*, + concurrency-thread-canceltype-asynchronous, + cppcoreguidelines-avoid-capturing-lambda-coroutines, + cppcoreguidelines-avoid-c-arrays, + cppcoreguidelines-avoid-goto, + cppcoreguidelines-avoid-reference-coroutine-parameters, + cppcoreguidelines-c-copy-assignment-signature, + cppcoreguidelines-explicit-virtual-functions, + cppcoreguidelines-interfaces-global-init, + cppcoreguidelines-narrowing-conversions, + cppcoreguidelines-no-malloc, + cppcoreguidelines-no-suspend-with-lock, + cppcoreguidelines-owning-memory, + cppcoreguidelines-prefer-member-initializer, + cppcoreguidelines-pro-bounds-array-to-pointer-decay, + cppcoreguidelines-pro-bounds-pointer-arithmetic, + cppcoreguidelines-pro-type-const-cast, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-pro-type-static-cast-downcast, + cppcoreguidelines-pro-type-union-access, + cppcoreguidelines-pro-type-vararg, + cppcoreguidelines-slicing, + cppcoreguidelines-virtual-class-destructor, + google-build-explicit-make-pair, + google-default-arguments, + google-explicit-constructor, + hicpp-exception-baseclass, + misc-confusable-identifiers, + misc-coroutine-hostile-raii, + misc-misleading-*, + misc-non-copyable-objects, + misc-throw-by-value-catch-by-reference, + misc-uniqueptr-reset-release, + modernize-avoid-*, + modernize-concat-nested-namespaces, + modernize-deprecated-*, + modernize-loop-convert, + modernize-macro-to-enum, + modernize-make-*, + modernize-pass-by-value, + modernize-raw-string-literal, + modernize-redundant-void-arg, + modernize-replace-*, + modernize-return-braced-init-list, + modernize-shrink-to-fit, + modernize-unary-static-assert, + modernize-use-auto, + modernize-use-bool-literals, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-use-equals-*, + modernize-use-nodiscard, + modernize-use-noexcept, + modernize-use-nullptr, + modernize-use-override, + modernize-use-transparent-functors, + modernize-use-uncaught-exceptions, + modernize-use-using, + performance-faster-string-find, + performance-for-range-copy, + performance-inefficient-*, + performance-move-constructor-init, + performance-noexcept-destructor, + performance-noexcept-swap, + performance-unnecessary-copy-initialization, + portability-restrict-system-includes, + portability-std-allocator-const, + readability-const-return-type, + readability-container-contains, + readability-container-size-empty, + readability-convert-member-functions-to-static, + readability-delete-null-pointer, + readability-duplicate-include, + readability-else-after-return, + readability-inconsistent-declaration-parameter-name, + readability-make-member-function-const, + readability-misleading-indentation, + readability-misplaced-array-index, + readability-redundant-*, + readability-simplify-subscript-expr, + readability-static-definition-in-anonymous-namespace, + readability-string-compare, + readability-uniqueptr-delete-release, + readability-use-*' diff --git a/libs/openal-soft/.github/workflows/ci.yml b/libs/openal-soft/.github/workflows/ci.yml index b1c3b413..071c93e5 100644 --- a/libs/openal-soft/.github/workflows/ci.yml +++ b/libs/openal-soft/.github/workflows/ci.yml @@ -106,9 +106,35 @@ jobs: libdbus-1-dev", build_type: "Release" } - + - { + name: "Android_armeabi-v7a-Release", + os: ubuntu-latest, + cmake_opts: "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ + -DALSOFT_EMBED_HRTF_DATA=TRUE \ + -DALSOFT_REQUIRE_OPENSL=ON", + build_type: "Release" + } + - { + name: "Android_arm64-v8a-Release", + os: ubuntu-latest, + cmake_opts: "-DANDRIOD_ABI=arm64-v8a \ + -DANDROID_PLATFORM=25 \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ + -DALSOFT_EMBED_HRTF_DATA=TRUE \ + -DALSOFT_REQUIRE_OPENSL=ON", + build_type: "Release" + } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: '0' + + - name: Get current commit tag, short hash, count and date + run: | + echo "CommitTag=$(git describe --tags --abbrev=0 --match *.*.*)" >> $env:GITHUB_ENV + echo "CommitHashShort=$(git rev-parse --short=8 HEAD)" >> $env:GITHUB_ENV + echo "CommitCount=$(git rev-list --count $env:GITHUB_REF_NAME)" >> $env:GITHUB_ENV + echo "CommitDate=$(git show -s --date=iso-local --format=%cd)" >> $env:GITHUB_ENV - name: Install Dependencies shell: bash @@ -133,8 +159,8 @@ jobs: cd build ctest - - name: Create Archive - if: ${{ matrix.config.os == 'windows-latest' }} + - name: Set up Windows artifacts + if: ${{ contains(matrix.config.name, 'Win') }} shell: bash run: | cd build @@ -143,10 +169,74 @@ jobs: cp ${{matrix.config.build_type}}/soft_oal.dll archive cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router - - name: Upload Archive - # Upload package as an artifact of this workflow. - uses: actions/upload-artifact@v3.1.2 - if: ${{ matrix.config.os == 'windows-latest' }} + - 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" + 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/*" diff --git a/libs/openal-soft/.github/workflows/makemhr.yml b/libs/openal-soft/.github/workflows/utils.yml similarity index 56% rename from libs/openal-soft/.github/workflows/makemhr.yml rename to libs/openal-soft/.github/workflows/utils.yml index 65486150..4683a4e3 100644 --- a/libs/openal-soft/.github/workflows/makemhr.yml +++ b/libs/openal-soft/.github/workflows/utils.yml @@ -1,31 +1,35 @@ -name: makemhr +name: utils on: push: paths: - - 'utils/makemhr/**' - - '.github/workflows/makemhr.yml' + - 'utils/**' + - 'examples/**' + - '.github/workflows/utils.yml' workflow_dispatch: env: BUILD_TYPE: Release + Branch: ${{github.ref_name}} jobs: Win64: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - name: Clone repo and submodules + run: git clone https://github.com/${{github.repository}}.git . --branch ${{env.Branch}} - - name: Get current date - run: echo "CurrentDate=$(date +'%Y-%m-%d')" >> $env:GITHUB_ENV - - - name: Get commit hash - run: echo "CommitHash=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV + - 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 --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git 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 @@ -52,25 +56,30 @@ jobs: - 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 makemhr artifact - uses: actions/upload-artifact@v3.1.2 + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: - name: makemhr + name: ${{env.CommitDate}}_utils-r${{env.CommitCount}}@${{env.CommitHashShort}} path: "Artifacts/" - name: Compress artifacts uses: papeloto/action-zip@v1 with: files: Artifacts/ - dest: "Release/makemhr.zip" + dest: "Release/utils.zip" - name: GitHub pre-release uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{secrets.GITHUB_TOKEN}}" - automatic_release_tag: "makemhr" + automatic_release_tag: "utils" prerelease: true - title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}" - files: "Release/makemhr.zip" + title: "[${{env.CommitDate}}] utils-r${{env.CommitCount}}@${{env.CommitHashShort}}" + files: "Release/utils.zip" diff --git a/libs/openal-soft/CMakeLists.txt b/libs/openal-soft/CMakeLists.txt index f2b7dcd5..060a7408 100644 --- a/libs/openal-soft/CMakeLists.txt +++ b/libs/openal-soft/CMakeLists.txt @@ -75,12 +75,12 @@ if(NOT CMAKE_DEBUG_POSTFIX) FORCE) endif() -set(DEFAULT_TARGET_PROPS +set(ALSOFT_STD_VERSION_PROPS # Require C++17. CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE - # Prefer C11, but support C99 and earlier when possible. - C_STANDARD 11) + # Prefer C17, but support earlier when necessary. + C_STANDARD 17) set(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake") @@ -93,13 +93,13 @@ include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) include(CheckCSourceCompiles) include(CheckCXXSourceCompiles) +include(CheckLinkerFlag) include(CheckStructHasMember) include(CMakePackageConfigHelpers) include(GNUInstallDirs) find_package(PkgConfig) -find_package(SDL2 QUIET) - +find_package(SDL3 QUIET) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) @@ -144,6 +144,23 @@ 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) + 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() + +add_subdirectory(fmt-11.1.1 EXCLUDE_FROM_ALL) + set(CPP_DEFS ) # C pre-processor, not C++ set(INC_PATHS ) @@ -163,6 +180,21 @@ if(WIN32) 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() @@ -181,8 +213,8 @@ if(NOT LIBTYPE) endif() set(LIB_MAJOR_VERSION "1") -set(LIB_MINOR_VERSION "23") -set(LIB_REVISION "1") +set(LIB_MINOR_VERSION "24") +set(LIB_REVISION "3") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) @@ -203,31 +235,6 @@ if(NOT HAVE_STDC_FORMAT_MACROS) set(CPP_DEFS ${CPP_DEFS} __STDC_FORMAT_MACROS) endif() -if(NOT WIN32) - # Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT) - if(NOT HAVE_POSIX_MEMALIGN_DEFAULT) - set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600") - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX) - if(NOT HAVE_POSIX_MEMALIGN_POSIX) - set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) - else() - set(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600) - endif() - endif() - unset(OLD_REQUIRED_FLAGS) -endif() - -# C99 has restrict, but C++ does not, so we can only utilize __restrict. -check_cxx_source_compiles("int *__restrict foo; -int main() { return 0; }" HAVE___RESTRICT) -if(HAVE___RESTRICT) - set(CPP_DEFS ${CPP_DEFS} RESTRICT=__restrict) -else() - set(CPP_DEFS ${CPP_DEFS} "RESTRICT=") -endif() - # Some systems may need libatomic for atomic functions to work set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES} atomic) @@ -252,12 +259,18 @@ if(ANDROID) endif() if(MSVC) - set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS) + # NOTE: _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR is temporary. When building on + # VS 2022 17.10 or newer, but using an older runtime, mutexes can crash + # when locked. Ideally the runtime should be updated on the system, but + # until the update becomes more widespread, this helps avoid some pain + # points. + set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH) set(C_FLAGS ${C_FLAGS} $<$:/permissive->) endif() - set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051) + set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051 + $<$:/EHsc> /utf-8) if(NOT DXSDK_DIR) string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") @@ -267,19 +280,6 @@ if(MSVC) if(DXSDK_DIR) message(STATUS "Using DirectX SDK directory: ${DXSDK_DIR}") endif() - - option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF) - if(FORCE_STATIC_VCRT) - foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif() - endforeach(flag_var) - endif() else() set(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align -Wpedantic @@ -295,17 +295,25 @@ else() endif() endif() - if(ALSOFT_WERROR) - set(C_FLAGS ${C_FLAGS} -Werror) + 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() - # We want RelWithDebInfo to actually include debug stuff (define _DEBUG - # instead of NDEBUG) - foreach(flag_var CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "-DNDEBUG") - string(REGEX REPLACE "-DNDEBUG" "-D_DEBUG" ${flag_var} "${${flag_var}}") - endif() - endforeach() + if(ALSOFT_WERROR) + set(C_FLAGS ${C_FLAGS} -Werror) + else() + set(C_FLAGS ${C_FLAGS} -Werror=undef) + endif() + + # NOTE: This essentially provides the equivalent of the C++26 feature to + # initialize all local variables with a non-0 bit pattern. Until C++26 is + # adopted, the [[gnu::uninitialized]] attribute will avoid the auto- + # initialization where necessary. + check_c_compiler_flag(-ftrivial-auto-var-init=pattern HAVE_FTRIVIAL_AUTO_VAR_INIT) + if(HAVE_FTRIVIAL_AUTO_VAR_INIT) + set(C_FLAGS ${C_FLAGS} -ftrivial-auto-var-init=pattern) + endif() check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO) if(HAVE_FNO_MATH_ERRNO) @@ -512,13 +520,9 @@ if(HAVE_SSE2) endif() -check_include_file(malloc.h HAVE_MALLOC_H) check_include_file(cpuid.h HAVE_CPUID_H) check_include_file(intrin.h HAVE_INTRIN_H) check_include_file(guiddef.h HAVE_GUIDDEF_H) -if(NOT HAVE_GUIDDEF_H) - check_include_file(initguid.h HAVE_INITGUID_H) -endif() # Some systems need libm for some math functions to work set(MATH_LIB ) @@ -599,12 +603,12 @@ if(NOT WIN32) endif() endif() -check_symbol_exists(getopt unistd.h HAVE_GETOPT) - # Common sources used by both the OpenAL implementation library, the OpenAL # router, and certain tools and examples. set(COMMON_OBJS + common/alassert.cpp + common/alassert.h common/albit.h common/alcomplex.cpp common/alcomplex.h @@ -624,6 +628,8 @@ set(COMMON_OBJS common/comptr.h common/dynload.cpp common/dynload.h + common/filesystem.cpp + common/filesystem.h common/flexarray.h common/intrusive_ptr.h common/opthelpers.h @@ -681,7 +687,6 @@ set(CORE_OBJS core/filters/nfc.h core/filters/splitter.cpp core/filters/splitter.h - core/fmt_traits.cpp core/fmt_traits.h core/fpu_ctrl.cpp core/fpu_ctrl.h @@ -731,17 +736,17 @@ if(NOT WIN32) 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() - 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() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) @@ -783,7 +788,6 @@ set(OPENAL_OBJS al/effects/reverb.cpp al/effects/vmorpher.cpp al/error.cpp - al/error.h al/event.cpp al/event.h al/extension.cpp @@ -885,8 +889,10 @@ set(HAVE_PULSEAUDIO 0) set(HAVE_COREAUDIO 0) set(HAVE_OPENSL 0) set(HAVE_OBOE 0) +set(HAVE_OTHERIO 0) set(HAVE_WAVE 0) set(HAVE_SDL2 0) +set(HAVE_SDL3 0) if(WIN32 OR HAVE_DLFCN_H) set(IS_LINKED "") @@ -988,17 +994,21 @@ if(NOT WIN32) endif() endif() - # Check SndIO backend - option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + # 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(SoundIO) - if(SOUNDIO_FOUND) + 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.h) - set(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS}) - set(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS}) + 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() @@ -1063,8 +1073,20 @@ if(WIN32) set(HAVE_WASAPI 1) set(BACKENDS "${BACKENDS} WASAPI,") set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) + + if(NOT ALSOFT_UWP) + set(EXTRA_LIBS avrt ${EXTRA_LIBS}) + endif() endif() endif() + + option(ALSOFT_BACKEND_OTHERIO "Enable OtherIO backend" OFF) + option(ALSOFT_REQUIRE_OTHERIO "Require OtherIO backend" OFF) + if(ALSOFT_BACKEND_OTHERIO) + set(HAVE_OTHERIO 1) + set(BACKENDS "${BACKENDS} OtherIO,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/otherio.cpp alc/backends/otherio.h) + endif() endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) message(FATAL_ERROR "Failed to enable required WinMM backend") @@ -1075,6 +1097,9 @@ endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() +if(ALSOFT_REQUIRE_OTHERIO AND NOT HAVE_OTHERIO) + message(FATAL_ERROR "Failed to enable required OtherIO backend") +endif() # Check JACK backend option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) @@ -1136,7 +1161,7 @@ if(ALSOFT_BACKEND_OBOE) if(ANDROID) set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") if(OBOE_SOURCE) - add_subdirectory(${OBOE_SOURCE} ./oboe) + add_subdirectory(${OBOE_SOURCE} ./oboe EXCLUDE_FROM_ALL) set(OBOE_TARGET oboe) else() find_package(oboe CONFIG) @@ -1154,9 +1179,6 @@ if(ALSOFT_BACKEND_OBOE) set(ALC_OBJS ${ALC_OBJS} alc/backends/oboe.cpp alc/backends/oboe.h) set(BACKENDS "${BACKENDS} Oboe,") set(EXTRA_LIBS ${OBOE_TARGET} ${EXTRA_LIBS}) - if(MEGA) - set(ALC_OBJS ${ALC_OBJS} opensl_latency.cpp) - endif() endif() endif() if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE) @@ -1171,8 +1193,8 @@ if(ALSOFT_BACKEND_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,") - set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS}) + set(BACKENDS "${BACKENDS} OpenSL${IS_LINKED},") + add_backend_libs(${OPENSL_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) endif() endif() @@ -1188,7 +1210,7 @@ if(ALSOFT_BACKEND_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.h) + 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() @@ -1197,11 +1219,29 @@ if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() -# Check for SDL2 backend -# Off by default, since it adds a runtime dependency +# 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) +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) @@ -1211,7 +1251,7 @@ if(ALSOFT_BACKEND_SDL2) message(STATUS "Could NOT find SDL2") endif() endif() -if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) +if(ALSOFT_REQUIRE_SDL2 AND NOT HAVE_SDL2) message(FATAL_ERROR "Failed to enable required SDL2 backend") endif() @@ -1253,7 +1293,7 @@ if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.g VERBATIM ) - add_custom_target(build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") + add_custom_target(alsoft.build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") else() set(GIT_BRANCH "UNKNOWN") set(GIT_COMMIT_HASH "unknown") @@ -1282,6 +1322,15 @@ if(ALSOFT_EMBED_HRTF_DATA) 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) @@ -1294,9 +1343,9 @@ if(ALSOFT_UTILS) endif() if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) - if(SDL2_FOUND) - find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) - endif() +endif() +if(ALSOFT_EXAMPLES AND SDL3_FOUND) + find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) endif() if(NOT WIN32) @@ -1318,12 +1367,14 @@ 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 target, add the link flag for it. Otherwise, - # it's a name intended to be dressed as -lname. + # 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() @@ -1334,28 +1385,53 @@ endif() configure_file( "${OpenAL_SOURCE_DIR}/config.h.in" "${OpenAL_BINARY_DIR}/config.h") +configure_file( + "${OpenAL_SOURCE_DIR}/config_backends.h.in" + "${OpenAL_BINARY_DIR}/config_backends.h") +configure_file( + "${OpenAL_SOURCE_DIR}/config_simd.h.in" + "${OpenAL_BINARY_DIR}/config_simd.h") configure_file( "${OpenAL_SOURCE_DIR}/openal.pc.in" "${OpenAL_BINARY_DIR}/openal.pc" @ONLY) -add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) -target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include - PUBLIC ${OpenAL_SOURCE_DIR}/common) -target_compile_definitions(alcommon PRIVATE ${CPP_DEFS}) -target_compile_options(alcommon PRIVATE ${C_FLAGS}) -set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) +add_library(alsoft.common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) +target_include_directories(alsoft.common PRIVATE ${OpenAL_SOURCE_DIR}/include + PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) +target_compile_definitions(alsoft.common PRIVATE ${CPP_DEFS}) +target_compile_options(alsoft.common PRIVATE ${C_FLAGS}) +target_link_libraries(alsoft.common PRIVATE alsoft::fmt) +set_target_properties(alsoft.common PROPERTIES ${ALSOFT_STD_VERSION_PROPS} + POSITION_INDEPENDENT_CODE TRUE) unset(HAS_ROUTER) set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. + +set(NEED_ANALYZE_SOURCE_FILES "") +foreach(obj ${CORE_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${COMMON_OBJS}) + IF (NOT ${obj} MATCHES "${CMAKE_BINARY_DIR}/default_hrtf.txt") + list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${obj}") + endif() +endforeach() +IF (ALSOFT_UTILS) + list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/openal-info.c") +endif() +SET(CLANG_TIDY_EXECUTABLE "clang-tidy") +if(DEFINED ENV{CLANG_TIDY_EXECUTABLE}) + SET(CLANG_TIDY_EXECUTABLE $ENV{CLANG_TIDY_EXECUTABLE}) +endif() +add_custom_target(clang-tidy-check ${CLANG_TIDY_EXECUTABLE} -format-style=file -p ${CMAKE_BINARY_DIR}/compile_commands.json ${NEED_ANALYZE_SOURCE_FILES} DEPENDS ${NEED_ANALYZE_SOURCE_FILES}) + # Build main library if(LIBTYPE STREQUAL "STATIC") add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) - target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB} + $) if(WIN32) # This option is for static linking OpenAL Soft into another project @@ -1380,7 +1456,7 @@ else() 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 alcommon ${LINKER_FLAGS}) + target_link_libraries(OpenAL PRIVATE alsoft.common ${LINKER_FLAGS} alsoft::fmt) target_include_directories(OpenAL PUBLIC $ @@ -1389,10 +1465,10 @@ else() ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} ) - set_target_properties(OpenAL PROPERTIES ${DEFAULT_TARGET_PROPS} PREFIX "" + set_target_properties(OpenAL PROPERTIES ${ALSOFT_STD_VERSION_PROPS} PREFIX "" OUTPUT_NAME ${LIBNAME}) - if(TARGET build_version) - add_dependencies(OpenAL build_version) + if(TARGET alsoft.build_version) + add_dependencies(OpenAL alsoft.build_version) endif() set(HAS_ROUTER 1) @@ -1411,24 +1487,30 @@ else() if(WIN32) set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") endif() - target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE alsoft.common ${LINKER_FLAGS} ${EXTRA_LIBS} + ${MATH_LIB} alsoft::fmt) if(ALSOFT_UWP) - 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.") + find_package(cppwinrt CONFIG) + if (TARGET Microsoft::CppWinRT) + target_link_libraries(${IMPL_TARGET} PRIVATE Microsoft::CppWinRT) + else() + set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") + + find_program(NUGET_EXE NAMES nuget) + if(NOT NUGET_EXE) + message("NUGET.EXE not found.") + message(FATAL_ERROR "Please install this executable, and run CMake again.") + endif() + + exec_program(${NUGET_EXE} + ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") + + set_target_properties(${IMPL_TARGET} PROPERTIES + VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props + ) + target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) endif() - - exec_program(${NUGET_EXE} - ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") - - set_target_properties(${IMPL_TARGET} PROPERTIES - VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props - ) - target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) endif() if(NOT WIN32 AND NOT APPLE) @@ -1473,8 +1555,6 @@ else() endif() endif() -set(OPENAL_LIB_NAME ${IMPL_TARGET} PARENT_SCOPE) - target_include_directories(${IMPL_TARGET} PUBLIC $ @@ -1489,18 +1569,18 @@ target_include_directories(${IMPL_TARGET} ${OpenAL_SOURCE_DIR}/common ) -set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} +set_target_properties(${IMPL_TARGET} PROPERTIES ${ALSOFT_STD_VERSION_PROPS} OUTPUT_NAME ${LIBNAME} VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" + 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 build_version) - add_dependencies(${IMPL_TARGET} build_version) +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") @@ -1514,7 +1594,7 @@ if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC" message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") endif() else() - target_link_options(OpenAL PRIVATE "-Wl,--output-def,OpenAL32.def") + 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 @@ -1623,7 +1703,7 @@ if(ALSOFT_UTILS) target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) target_compile_options(openal-info PRIVATE ${C_FLAGS}) target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) - set_target_properties(openal-info PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(openal-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) endif() @@ -1634,30 +1714,31 @@ if(ALSOFT_UTILS) target_include_directories(uhjdecoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjdecoder PUBLIC alcommon - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) - set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(uhjdecoder PUBLIC alsoft.common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(uhjdecoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(uhjencoder utils/uhjencoder.cpp) target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjencoder PUBLIC alcommon - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) - set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(uhjencoder PUBLIC alsoft.common + PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(uhjencoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() if(MYSOFA_FOUND) set(SOFA_SUPPORT_SRCS utils/sofa-support.cpp utils/sofa-support.h) - add_library(sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) - target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS}) - target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) - target_compile_options(sofa-support PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) - set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS}) + add_library(alsoft.sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) + target_compile_definitions(alsoft.sofa-support PRIVATE ${CPP_DEFS}) + target_include_directories(alsoft.sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) + target_compile_options(alsoft.sofa-support PRIVATE ${C_FLAGS}) + target_link_libraries(alsoft.sofa-support PUBLIC alsoft.common MySOFA::MySOFA + PRIVATE ${LINKER_FLAGS} alsoft::fmt) + set_target_properties(alsoft.sofa-support PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) set(MAKEMHR_SRCS utils/makemhr/loaddef.cpp @@ -1666,16 +1747,14 @@ if(ALSOFT_UTILS) utils/makemhr/loadsofa.h utils/makemhr/makemhr.cpp utils/makemhr/makemhr.h) - if(NOT HAVE_GETOPT) - set(MAKEMHR_SRCS ${MAKEMHR_SRCS} utils/getopt.c utils/getopt.h) - endif() 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} sofa-support ${UNICODE_FLAG}) - set_target_properties(makemhr PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG} + alsoft::fmt) + set_target_properties(makemhr PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) endif() @@ -1685,8 +1764,9 @@ if(ALSOFT_UTILS) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils) target_compile_options(sofa-info PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) - set_target_properties(sofa-info PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} alsoft.sofa-support + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(sofa-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() message(STATUS "Building utility programs") @@ -1698,91 +1778,108 @@ endif() # Add a static library with common functions used by multiple example targets -add_library(al-excommon STATIC EXCLUDE_FROM_ALL +add_library(alsoft.excommon STATIC EXCLUDE_FROM_ALL examples/common/alhelpers.c examples/common/alhelpers.h) -target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS}) -target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common) -target_compile_options(al-excommon PUBLIC ${C_FLAGS}) -target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) -set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS}) +target_compile_definitions(alsoft.excommon PUBLIC ${CPP_DEFS}) +target_include_directories(alsoft.excommon PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) +target_compile_options(alsoft.excommon PUBLIC ${C_FLAGS}) +target_link_libraries(alsoft.excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) +set_target_properties(alsoft.excommon PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) - target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG}) - set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} alsoft.excommon + ${UNICODE_FLAG}) + set_target_properties(altonegen PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alrecord examples/alrecord.c) - target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG}) - set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG}) + set_target_properties(alrecord PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) + + add_executable(aldebug examples/aldebug.cpp) + target_link_libraries(aldebug PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG} + alsoft::fmt) + set_target_properties(aldebug PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) + + add_executable(allafplay examples/allafplay.cpp) + target_link_libraries(allafplay PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(allafplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) - set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord) + 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 al-excommon + target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstream examples/alstream.c) - target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alstream PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alreverb examples/alreverb.c) - target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(alreverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(almultireverb examples/almultireverb.c) target_link_libraries(almultireverb - PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) - set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) + set_target_properties(almultireverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(allatency examples/allatency.c) - target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon + target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) - set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS}) + set_target_properties(allatency PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alhrtf examples/alhrtf.c) target_link_libraries(alhrtf - PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) - set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) + set_target_properties(alhrtf PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstreamcb examples/alstreamcb.cpp) - target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon - ${UNICODE_FLAG}) - set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(alstreamcb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) + + add_executable(aldirect examples/aldirect.cpp) + target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon + ${UNICODE_FLAG} alsoft::fmt) + set_target_properties(aldirect PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alconvolve examples/alconvolve.c) - target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile - al-excommon ${UNICODE_FLAG}) - set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS}) + target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alsoft.common SndFile::SndFile + alsoft.excommon ${UNICODE_FLAG}) + set_target_properties(alconvolve PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency - alhrtf) + alhrtf aldirect) endif() message(STATUS "Building SndFile example programs") endif() - if(SDL2_FOUND) + # 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} SDL2::SDL2 al-excommon ${MATH_LIB}) - set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SDL3::SDL3 alsoft.excommon ${MATH_LIB}) + set_target_properties(alloopback PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) endif() - message(STATUS "Building SDL example programs") - set(FFVER_OK FALSE) if(FFMPEG_FOUND) set(FFVER_OK TRUE) @@ -1811,19 +1908,19 @@ if(ALSOFT_EXAMPLES) add_executable(alffplay examples/alffplay.cpp) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon) - set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS}) + PRIVATE ${LINKER_FLAGS} SDL3::SDL3 ${FFMPEG_LIBRARIES} alsoft.excommon alsoft::fmt) + set_target_properties(alffplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) endif() - message(STATUS "Building SDL+FFmpeg example programs") + message(STATUS "Building SDL3+FFmpeg example programs") endif() endif() message(STATUS "") endif() -if (ALSOFT_TESTS) +if(ALSOFT_TESTS) add_subdirectory(tests) endif() @@ -1834,7 +1931,3 @@ if(EXTRA_INSTALLS) LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() - -if(MEGA) - install(TARGETS ${IMPL_TARGET} RUNTIME DESTINATION . LIBRARY DESTINATION .) -endif() diff --git a/libs/openal-soft/ChangeLog b/libs/openal-soft/ChangeLog index e4236f85..c3a0ab6b 100644 --- a/libs/openal-soft/ChangeLog +++ b/libs/openal-soft/ChangeLog @@ -1,3 +1,137 @@ +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. diff --git a/libs/openal-soft/README.md b/libs/openal-soft/README.md index dac53e71..09f9ec24 100644 --- a/libs/openal-soft/README.md +++ b/libs/openal-soft/README.md @@ -78,15 +78,26 @@ 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. diff --git a/libs/openal-soft/al/auxeffectslot.cpp b/libs/openal-soft/al/auxeffectslot.cpp index 53cbdf7e..d405f18e 100644 --- a/libs/openal-soft/al/auxeffectslot.cpp +++ b/libs/openal-soft/al/auxeffectslot.cpp @@ -23,7 +23,6 @@ #include "auxeffectslot.h" #include -#include #include #include #include @@ -32,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -55,53 +53,52 @@ #include "buffer.h" #include "core/buffer_storage.h" #include "core/device.h" +#include "core/except.h" #include "core/fpu_ctrl.h" #include "core/logging.h" #include "direct_defs.h" #include "effect.h" -#include "error.h" #include "flexarray.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/fx_slot_index.h" -#include "eax/utils.h" #endif namespace { using SubListAllocator = al::allocator>; -EffectStateFactory *getFactoryByType(EffectSlotType type) +[[nodiscard]] +auto getFactoryByType(EffectSlotType type) -> EffectStateFactory* { switch(type) { case EffectSlotType::None: return NullStateFactory_getFactory(); - case EffectSlotType::EAXReverb: return ReverbStateFactory_getFactory(); - case EffectSlotType::Reverb: return StdReverbStateFactory_getFactory(); - case EffectSlotType::Autowah: return AutowahStateFactory_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 FlangerStateFactory_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(); - case EffectSlotType::DedicatedDialog: return DedicatedDialogStateFactory_getFactory(); - case EffectSlotType::DedicatedLFE: return DedicatedLfeStateFactory_getFactory(); - case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory(); } return nullptr; } -inline auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* +[[nodiscard]] +auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -114,7 +111,8 @@ inline auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffec return al::to_address(sublist.EffectSlots->begin() + slidx); } -inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* +[[nodiscard]] +inline auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -127,7 +125,8 @@ inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* return al::to_address(sublist.Effects->begin() + slidx); } -inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* +[[nodiscard]] +inline auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -145,42 +144,44 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co { if(auxslots.empty()) return; EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; - size_t newcount{curarray->size() + auxslots.size()}; + if((curarray->size()>>1) > std::numeric_limits::max()-auxslots.size()) + throw std::runtime_error{"Too many active effect slots"}; - /* Insert the new effect slots into the head of the array, followed by the - * existing ones. + size_t newcount{(curarray->size()>>1) + auxslots.size()}; + if(newcount > std::numeric_limits::max()>>1) + throw std::runtime_error{"Too many active effect slots"}; + + /* Insert the new effect slots into the head of the new array, followed by + * the existing ones. */ - auto newarray = EffectSlot::CreatePtrArray(newcount); - auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), + auto newarray = EffectSlot::CreatePtrArray(newcount<<1); + auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), std::mem_fn(&ALeffectslot::mSlot)); - std::copy(curarray->begin(), curarray->end(), slotiter); + new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end); /* Remove any duplicates (first instance of each will be kept). */ - auto last = newarray->end(); for(auto start=newarray->begin()+1;;) { - last = std::remove(start, last, *(start-1)); - if(start == last) break; + new_end = std::remove(start, new_end, *(start-1)); + if(start == new_end) break; ++start; } - newcount = static_cast(std::distance(newarray->begin(), last)); + newcount = static_cast(std::distance(newarray->begin(), new_end)); /* Reallocate newarray if the new size ended up smaller from duplicate * removal. */ - if(newcount < newarray->size()) UNLIKELY + if(newcount < newarray->size()>>1) UNLIKELY { auto oldarray = std::move(newarray); - newarray = EffectSlot::CreatePtrArray(newcount); - std::copy_n(oldarray->begin(), newcount, newarray->begin()); + newarray = EffectSlot::CreatePtrArray(newcount<<1); + new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newcount, nullptr); + std::fill(new_end, newarray->end(), nullptr); auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), std::memory_order_acq_rel); std::ignore = context->mDevice->waitForMix(); - - std::destroy_n(oldarray->end(), oldarray->size()); } void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) @@ -193,7 +194,7 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext */ auto newarray = EffectSlot::CreatePtrArray(curarray->size()); - auto new_end = std::copy(curarray->begin(), curarray->end(), newarray->begin()); + auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin()); /* Remove elements from newarray that match any ID in slotids. */ for(const ALeffectslot *auxslot : auxslots) { @@ -204,22 +205,21 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext /* Reallocate with the new size. */ auto newsize = static_cast(std::distance(newarray->begin(), new_end)); - if(newsize != newarray->size()) LIKELY + if(newsize < newarray->size()>>1) LIKELY { auto oldarray = std::move(newarray); - newarray = EffectSlot::CreatePtrArray(newsize); - std::copy_n(oldarray->begin(), newsize, newarray->begin()); + newarray = EffectSlot::CreatePtrArray(newsize<<1); + new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newsize, nullptr); + std::fill(new_end, newarray->end(), nullptr); auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), std::memory_order_acq_rel); std::ignore = context->mDevice->waitForMix(); - - std::destroy_n(curarray->end(), curarray->size()); } +[[nodiscard]] constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) @@ -237,15 +237,16 @@ constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType 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::EAXReverb; - case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::DedicatedLFE; - case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::DedicatedDialog; + 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: 0x%04x\n", type); + ERR("Unhandled effect enum: {:#04x}", as_unsigned(type)); return EffectSlotType::None; } +[[nodiscard]] auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool try { size_t count{std::accumulate(context->mEffectSlotList.cbegin(), @@ -270,7 +271,8 @@ catch(...) { return false; } -ALeffectslot *AllocEffectSlot(ALCcontext *context) +[[nodiscard]] +auto AllocEffectSlot(ALCcontext *context) -> ALeffectslot* { auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), [](const EffectSlotSubList &entry) noexcept -> bool @@ -325,20 +327,21 @@ FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context ALuint *effectslots) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} effect slots", n); if(n <= 0) UNLIKELY return; - std::lock_guard slotlock{context->mEffectSlotLock}; - ALCdevice *device{context->mALDevice.get()}; + auto slotlock = std::lock_guard{context->mEffectSlotLock}; + auto *device = context->mALDevice.get(); const al::span eids{effectslots, static_cast(n)}; - if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) - throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", - device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, 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())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effectslot{}", n, + (n==1) ? "" : "s"); std::vector slots; try { @@ -358,16 +361,18 @@ try { } } catch(std::exception& e) { - ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what()); + ERR("Exception allocating effectslot {} of {}: {}", slots.size()+1, n, e.what()); auto delete_effectslot = [context](ALeffectslot *slot) -> void { FreeEffectSlot(context, slot); }; std::for_each(slots.begin(), slots.end(), delete_effectslot); - throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n, - e.what()}; + context->throw_error(AL_INVALID_OPERATION, "Exception allocating {} effectslots: {}", n, + e.what()); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) @@ -375,7 +380,7 @@ FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *cont const ALuint *effectslots) noexcept try { if(n < 0) UNLIKELY - throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} effect slots", n); if(n <= 0) UNLIKELY return; std::lock_guard slotlock{context->mEffectSlotLock}; @@ -383,10 +388,10 @@ try { { ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", *effectslots); if(slot->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - *effectslots}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", + *effectslots); RemoveActiveEffectSlots({&slot, 1u}, context); FreeEffectSlot(context, slot); @@ -401,10 +406,9 @@ try { { ALeffectslot *slot{LookupEffectSlot(context, eid)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", eid); if(slot->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - eid}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", eid); return slot; }; std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); @@ -420,8 +424,10 @@ try { std::for_each(eids.begin(), eids.end(), delete_effectslot); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) @@ -435,112 +441,32 @@ FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *cont } -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - - std::lock_guard slotlock{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - if(slot->mState == SlotState::Playing) - return; - - slot->mPropsDirty = false; - slot->updateProps(context.get()); - - AddActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Playing; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); } -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto ids = al::span{slotids, static_cast(n)}; - auto slots = std::vector(ids.size()); - std::lock_guard slotlock{context->mEffectSlotLock}; - try { - auto lookupslot = [&context](const ALuint id) -> ALeffectslot* - { - ALeffectslot *slot{LookupEffectSlot(context.get(), id)}; - if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; - - if(slot->mState != SlotState::Playing) - { - slot->mPropsDirty = false; - slot->updateProps(context.get()); - } - return slot; - }; - std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot); - } - catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); - return; - } - - AddActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Playing; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); } -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - - std::lock_guard slotlock{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - - RemoveActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Stopped; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); } -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Stopping %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto ids = al::span{slotids, static_cast(n)}; - auto slots = std::vector(ids.size()); - std::lock_guard slotlock{context->mEffectSlotLock}; - try { - auto lookupslot = [&context](const ALuint id) -> ALeffectslot* - { - if(ALeffectslot *slot{LookupEffectSlot(context.get(), id)}) - return slot; - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; - }; - std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot); - } - catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); - return; - } - - RemoveActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Stopped; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); } @@ -553,7 +479,7 @@ try { ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); ALeffectslot *target{}; ALenum err{}; @@ -561,20 +487,20 @@ try { { case AL_EFFECTSLOT_EFFECT: { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; - ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; + auto *effect = value ? LookupEffect(device, static_cast(value)) : nullptr; if(effect) err = slot->initEffect(effect->id, effect->type, effect->Props, context); else { if(value != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", value); err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); } } if(err != AL_NO_ERROR) - throw al::context_error{err, "Effect initialization failed"}; + context->throw_error(err, "Effect initialization failed"); if(slot->mState == SlotState::Initial) UNLIKELY { @@ -590,8 +516,7 @@ try { case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) - throw al::context_error{AL_INVALID_VALUE, - "Effect slot auxiliary send auto out of range"}; + context->throw_error(AL_INVALID_VALUE, "Effect slot auxiliary send auto out of range"); if(!(slot->AuxSendAuto == !!value)) LIKELY { slot->AuxSendAuto = !!value; @@ -602,7 +527,7 @@ try { case AL_EFFECTSLOT_TARGET_SOFT: target = LookupEffectSlot(context, static_cast(value)); if(value && !target) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; + context->throw_error(AL_INVALID_VALUE, "Invalid effect slot target ID {}", value); if(slot->Target == target) UNLIKELY return; if(target) @@ -611,9 +536,9 @@ try { while(checker && checker != slot) checker = checker->Target; if(checker) - throw al::context_error{AL_INVALID_OPERATION, - "Setting target of effect slot ID %u to %u creates circular chain", slot->id, - target->id}; + context->throw_error(AL_INVALID_OPERATION, + "Setting target of effect slot ID {} to {} creates circular chain", slot->id, + target->id); } if(ALeffectslot *oldtarget{slot->Target}) @@ -634,30 +559,67 @@ try { return; case AL_BUFFER: - if(slot->mState == SlotState::Playing) - throw al::context_error{AL_INVALID_OPERATION, - "Setting buffer on playing effect slot %u", slot->id}; - if(ALbuffer *buffer{slot->Buffer}) { - if(buffer->id == static_cast(value)) UNLIKELY + if(buffer->id == static_cast(value)) return; } - else if(value == 0) UNLIKELY + else if(value == 0) return; + if(slot->mState == SlotState::Playing) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard bufferlock{device->BufferLock}; + EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)}; + assert(factory); + al::intrusive_ptr state{factory->create()}; + + auto *device = context->mALDevice.get(); + auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); if(!buffer) - throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); if(buffer->mCallback) - throw al::context_error{AL_INVALID_OPERATION, - "Callback buffer not valid for effects"}; + context->throw_error(AL_INVALID_OPERATION, + "Callback buffer not valid for effects"); + + IncrementRef(buffer->ref); + } + + /* Stop the effect slot from processing while we switch buffers. */ + RemoveActiveEffectSlots({&slot, 1}, context); + + if(ALbuffer *oldbuffer{slot->Buffer}) + DecrementRef(oldbuffer->ref); + slot->Buffer = buffer; + bufferlock.unlock(); + + state->mOutTarget = device->Dry.Buffer; + { + FPUCtl mixer_mode{}; + state->deviceUpdate(device, buffer); + } + slot->Effect.State = std::move(state); + + slot->mPropsDirty = false; + slot->updateProps(context); + AddActiveEffectSlots({&slot, 1}, context); + } + else + { + auto *device = context->mALDevice.get(); + auto bufferlock = std::unique_lock{device->BufferLock}; + ALbuffer *buffer{}; + if(value) + { + buffer = LookupBuffer(device, static_cast(value)); + if(!buffer) + context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); + if(buffer->mCallback) + context->throw_error(AL_INVALID_OPERATION, + "Callback buffer not valid for effects"); IncrementRef(buffer->ref); } @@ -665,22 +627,26 @@ try { if(ALbuffer *oldbuffer{slot->Buffer}) DecrementRef(oldbuffer->ref); slot->Buffer = buffer; + bufferlock.unlock(); FPUCtl mixer_mode{}; auto *state = slot->Effect.State.get(); state->deviceUpdate(device, buffer); + slot->mPropsDirty = true; } - UpdateProps(slot, context); return; case AL_EFFECTSLOT_STATE_SOFT: - throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"}; + context->throw_error(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) @@ -701,16 +667,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) @@ -722,13 +687,13 @@ try { ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { case AL_EFFECTSLOT_GAIN: if(!(value >= 0.0f && value <= 1.0f)) - throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Effect slot gain {} out of range", value); if(!(slot->Gain == value)) LIKELY { slot->Gain = value; @@ -737,10 +702,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) @@ -757,16 +725,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -777,7 +744,7 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { @@ -808,10 +775,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) @@ -832,16 +802,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot = LookupEffectSlot(context, effectslot); if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) @@ -851,19 +820,20 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { - case AL_EFFECTSLOT_GAIN: - *value = slot->Gain; - return; + case AL_EFFECTSLOT_GAIN: *value = slot->Gain; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) @@ -880,16 +850,15 @@ try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -931,13 +900,13 @@ ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const Effect EffectStateFactory *factory{getFactoryByType(newtype)}; if(!factory) { - ERR("Failed to find factory for effect slot type %d\n", static_cast(newtype)); + ERR("Failed to find factory for effect slot type {}", + int{al::to_underlying(newtype)}); return AL_INVALID_ENUM; } al::intrusive_ptr state{factory->create()}; - ALCdevice *device{context->mALDevice.get()}; - std::unique_lock statelock{device->StateLock}; + auto *device = context->mALDevice.get(); state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; @@ -1006,7 +975,7 @@ void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name auto slot = LookupEffectSlot(context, id); if(!slot) - throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id); context->mEffectSlotNames.insert_or_assign(id, name); } @@ -1023,7 +992,7 @@ void UpdateAllEffectSlotProps(ALCcontext *context) usemask &= ~(1_u64 << idx); auto &slot = (*sublist.EffectSlots)[idx]; - if(slot.mState != SlotState::Stopped && std::exchange(slot.mPropsDirty, false)) + if(std::exchange(slot.mPropsDirty, false)) slot.updateProps(context); } } @@ -1046,51 +1015,51 @@ EffectSlotSubList::~EffectSlotSubList() EffectSlots = nullptr; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index) { if(index >= EAX_MAX_FXSLOTS) eax_fail("Index out of range."); - eax_al_context_ = &al_context; - eax_fx_slot_index_ = index; + mEaxALContext = &al_context; + mEaxFXSlotIndex = index; eax_fx_slot_set_defaults(); - eax_effect_ = std::make_unique(); - if(index == 0) eax_effect_->init(); - else if(index == 1) eax_effect_->init(); - else eax_effect_->init(); + mEaxEffect = std::make_unique(); + if(index == 0) mEaxEffect->init(); + else if(index == 1) mEaxEffect->init(); + else mEaxEffect->init(); } void ALeffectslot::eax_commit() { - if(eax_df_ != EaxDirtyFlags{}) + if(mEaxDf.any()) { - auto df = EaxDirtyFlags{}; - switch(eax_version_) + auto df = std::bitset{}; + switch(mEaxVersion) { case 1: case 2: case 3: - eax5_fx_slot_commit(eax123_, df); + eax5_fx_slot_commit(mEax123, df); break; case 4: eax4_fx_slot_commit(df); break; case 5: - eax5_fx_slot_commit(eax5_, df); + eax5_fx_slot_commit(mEax5, df); break; } - eax_df_ = EaxDirtyFlags{}; + mEaxDf.reset(); - if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) + if(df.test(eax_volume_dirty_bit)) eax_fx_slot_set_volume(); - if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) + if(df.test(eax_flags_dirty_bit)) eax_fx_slot_set_flags(); } - if(eax_effect_->commit(eax_version_)) - eax_set_efx_slot_effect(*eax_effect_); + if(mEaxEffect->commit(mEaxVersion)) + eax_set_efx_slot_effect(*mEaxEffect); } [[noreturn]] void ALeffectslot::eax_fail(const char* message) @@ -1153,7 +1122,7 @@ ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid) const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept { - switch(eax_fx_slot_index_) + switch(mEaxFXSlotIndex) { case 0: return EAX_REVERB_EFFECT; case 1: return EAX_CHORUS_EFFECT; @@ -1166,7 +1135,7 @@ long ALeffectslot::eax_get_eax_default_lock() const noexcept return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; } -void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept +void ALeffectslot::eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; @@ -1174,7 +1143,7 @@ void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; } -void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept +void ALeffectslot::eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; @@ -1186,14 +1155,14 @@ void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept void ALeffectslot::eax_fx_slot_set_defaults() { - eax5_fx_slot_set_defaults(eax123_.i); - eax4_fx_slot_set_defaults(eax4_.i); - eax5_fx_slot_set_defaults(eax5_.i); - eax_ = eax5_.i; - eax_df_ = EaxDirtyFlags{}; + 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 ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { @@ -1217,7 +1186,7 @@ void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) } } -void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { @@ -1251,8 +1220,8 @@ void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const { switch(call.get_version()) { - case 4: eax4_fx_slot_get(call, eax4_.i); break; - case 5: eax5_fx_slot_get(call, eax5_.i); break; + 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(); } } @@ -1265,7 +1234,7 @@ bool ALeffectslot::eax_get(const EaxCall& call) eax_fx_slot_get(call); break; case EaxCallPropertySetId::fx_slot_effect: - eax_effect_->get(call); + mEaxEffect->get(call); break; default: eax_fail_unknown_property_id(); @@ -1278,19 +1247,19 @@ void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) { if(!IsValidEffectType(altype)) altype = AL_EFFECT_NULL; - eax_effect_->set_defaults(version, altype); + mEaxEffect->set_defaults(version, altype); } void ALeffectslot::eax_fx_slot_set_volume() { - const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); + const auto volume = std::clamp(mEax.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(static_cast(volume)); eax_set_efx_slot_gain(gain); } void ALeffectslot::eax_fx_slot_set_environment_flag() { - eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); + eax_set_efx_slot_send_auto((mEax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); } void ALeffectslot::eax_fx_slot_set_flags() @@ -1303,11 +1272,11 @@ void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call) eax4_fx_slot_ensure_unlocked(); const auto& src = call.get_value(); Eax4AllValidator{}(src); - auto& dst = eax4_.i; - eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. - eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); + 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; } @@ -1315,30 +1284,30 @@ void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call) { const auto& src = call.get_value(); Eax5AllValidator{}(src); - auto& dst = eax5_.i; - eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. - eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{}); - eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{}); + 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; } bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { - static constexpr auto dirty_bits = - eax_occlusion_dirty_bit | - eax_occlusion_lf_ratio_dirty_bit | - eax_flags_dirty_bit; - - return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; + 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. bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) { - auto& dst = eax4_.i; + auto& dst = mEax4.i; switch(call.get_property_id()) { @@ -1346,24 +1315,25 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) break; case EAXFXSLOT_ALLPARAMETERS: eax4_fx_slot_set_all(call); - if((eax_df_ & 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_LOADEFFECT: eax4_fx_slot_ensure_unlocked(); - eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); - if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_set_dirty(call, + dst.guidLoadEffect, mEaxDf); + 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_df_); + eax_fx_slot_set(call, dst.lVolume, mEaxDf); break; case EAXFXSLOT_LOCK: eax4_fx_slot_ensure_unlocked(); - eax_fx_slot_set(call, dst.lLock, eax_df_); + eax_fx_slot_set(call, dst.lLock, mEaxDf); break; case EAXFXSLOT_FLAGS: - eax_fx_slot_set(call, dst.ulFlags, eax_df_); + eax_fx_slot_set(call, dst.ulFlags, mEaxDf); break; default: eax_fail_unknown_property_id(); @@ -1375,7 +1345,7 @@ bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) { - auto& dst = eax5_.i; + auto& dst = mEax5.i; switch(call.get_property_id()) { @@ -1383,28 +1353,31 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) break; case EAXFXSLOT_ALLPARAMETERS: eax5_fx_slot_set_all(call); - if((eax_df_ & 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_LOADEFFECT: - eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); - if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_set_dirty(call, + dst.guidLoadEffect, mEaxDf); + 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_df_); + eax_fx_slot_set(call, dst.lVolume, mEaxDf); break; case EAXFXSLOT_LOCK: - eax_fx_slot_set(call, dst.lLock, eax_df_); + eax_fx_slot_set(call, dst.lLock, mEaxDf); break; case EAXFXSLOT_FLAGS: - eax_fx_slot_set(call, dst.ulFlags, eax_df_); + eax_fx_slot_set(call, dst.ulFlags, mEaxDf); break; case EAXFXSLOT_OCCLUSION: - eax_fx_slot_set(call, dst.lOcclusion, eax_df_); + eax_fx_slot_set(call, dst.lOcclusion, + mEaxDf); break; case EAXFXSLOT_OCCLUSIONLFRATIO: - eax_fx_slot_set(call, dst.flOcclusionLFRatio, eax_df_); + eax_fx_slot_set(call, + dst.flOcclusionLFRatio, mEaxDf); break; default: eax_fail_unknown_property_id(); @@ -1416,7 +1389,7 @@ bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax_fx_slot_set(const EaxCall& call) { - switch (call.get_version()) + switch(call.get_version()) { case 4: return eax4_fx_slot_set(call); case 5: return eax5_fx_slot_set(call); @@ -1432,39 +1405,41 @@ bool ALeffectslot::eax_set(const EaxCall& call) switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; - case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break; + case EaxCallPropertySetId::fx_slot_effect: mEaxEffect->set(call); break; default: eax_fail_unknown_property_id(); } const auto version = call.get_version(); - if(eax_version_ != version) - eax_df_ = ~EaxDirtyFlags{}; - eax_version_ = version; + if(mEaxVersion != version) + mEaxDf.set(); + mEaxVersion = version; return ret; } -void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) +void ALeffectslot::eax4_fx_slot_commit(std::bitset& dst_df) { - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock); - eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::lLock); + eax_fx_slot_commit_property(mEax4, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); - auto& dst_i = eax_; + auto& dst_i = mEax; - if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { - dst_df |= eax_occlusion_dirty_bit; + 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 |= eax_occlusion_lf_ratio_dirty_bit; + if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) + { + dst_df.set(eax_occlusion_lf_ratio_dirty_bit); dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } } -void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) +void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, std::bitset& dst_df) { eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume); @@ -1479,18 +1454,19 @@ void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect) #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, - eax_al_context_); - - if(error != AL_NO_ERROR) { - ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); + mEaxALContext); + if(error != AL_NO_ERROR) + { + ERR(EAX_PREFIX "Failed to initialize an effect."); return; } - if(mState == SlotState::Initial) { + if(mState == SlotState::Initial) + { mPropsDirty = false; - updateProps(eax_al_context_); + updateProps(mEaxALContext); auto effect_slot_ptr = this; - AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); + AddActiveEffectSlots({&effect_slot_ptr, 1}, mEaxALContext); mState = SlotState::Playing; return; } @@ -1516,7 +1492,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) if(gain == Gain) return; if(gain < 0.0f || gain > 1.0f) - ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); + ERR(EAX_PREFIX "Slot gain out of range ({:f})", gain); Gain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; @@ -1526,8 +1502,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { - assert(effect_slot); - eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); + eax_delete_al_effect_slot(*effect_slot->mEaxALContext, *effect_slot); } EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) @@ -1537,13 +1512,15 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) std::lock_guard slotlock{context.mEffectSlotLock}; auto& device = *context.mALDevice; - if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { - ERR(EAX_PREFIX "%s\n", "Out of memory."); + if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) + { + ERR(EAX_PREFIX "Out of memory."); return nullptr; } - if(!EnsureEffectSlots(&context, 1)) { - ERR(EAX_PREFIX "%s\n", "Failed to ensure."); + if(!EnsureEffectSlots(&context, 1)) + { + ERR(EAX_PREFIX "Failed to ensure."); return nullptr; } @@ -1560,7 +1537,7 @@ void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot) if(effect_slot.ref.load(std::memory_order_relaxed) != 0) { - ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); + ERR(EAX_PREFIX "Deleting in-use effect slot {}.", effect_slot.id); return; } diff --git a/libs/openal-soft/al/auxeffectslot.h b/libs/openal-soft/al/auxeffectslot.h index 4c7d5733..9c903cf5 100644 --- a/libs/openal-soft/al/auxeffectslot.h +++ b/libs/openal-soft/al/auxeffectslot.h @@ -1,8 +1,11 @@ #ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H +#include "config.h" + #include #include +#include #include #include #include @@ -16,7 +19,7 @@ #include "core/effectslot.h" #include "intrusive_ptr.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "eax/api.h" #include "eax/call.h" @@ -28,7 +31,7 @@ struct ALbuffer; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX class EaxFxSlotException : public EaxException { public: explicit EaxFxSlotException(const char* message) @@ -37,10 +40,8 @@ public: }; #endif // ALSOFT_EAX -enum class SlotState : ALenum { - Initial = AL_INITIAL, - Playing = AL_PLAYING, - Stopped = AL_STOPPED, +enum class SlotState : bool { + Initial, Playing, }; struct ALeffectslot { @@ -52,7 +53,7 @@ struct ALeffectslot { struct EffectData { EffectSlotType Type{EffectSlotType::None}; - EffectProps Props{}; + EffectProps Props; al::intrusive_ptr State; }; @@ -69,7 +70,7 @@ struct ALeffectslot { /* Self ID */ ALuint id{}; - ALeffectslot(ALCcontext *context); + explicit ALeffectslot(ALCcontext *context); ALeffectslot(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); @@ -81,13 +82,14 @@ struct ALeffectslot { static void SetName(ALCcontext *context, ALuint id, std::string_view name); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX public: void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } - [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& - { return eax_; } + [[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 @@ -96,25 +98,24 @@ public: void eax_commit(); private: - static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0; - static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1; - static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2; - static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3; - static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4; - static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5; + 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; - using Eax4Props = EAX40FXSLOTPROPERTIES; - struct Eax4State { - Eax4Props i; // Immediate. + EAX40FXSLOTPROPERTIES i; // Immediate. }; - using Eax5Props = EAX50FXSLOTPROPERTIES; - struct Eax5State { - Eax5Props i; // Immediate. + EAX50FXSLOTPROPERTIES i; // Immediate. }; struct EaxRangeValidator { @@ -194,6 +195,17 @@ private: } }; + struct Eax5FlagsValidator { + void operator()(unsigned long ulFlags) const + { + EaxRangeValidator{}( + "Flags", + ulFlags, + 0UL, + ~EAX50FXSLOTFLAGS_RESERVED); + } + }; + struct Eax5OcclusionValidator { void operator()(long lOcclusion) const { @@ -216,35 +228,27 @@ private: } }; - struct Eax5FlagsValidator { - void operator()(unsigned long ulFlags) const - { - EaxRangeValidator{}( - "Flags", - ulFlags, - 0UL, - ~EAX50FXSLOTFLAGS_RESERVED); - } - }; - struct Eax5AllValidator { void operator()(const EAX50FXSLOTPROPERTIES& all) const { - Eax4AllValidator{}(static_cast(all)); + Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); + Eax4VolumeValidator{}(all.lVolume); + Eax4LockValidator{}(all.lLock); + Eax5FlagsValidator{}(all.ulFlags); Eax5OcclusionValidator{}(all.lOcclusion); Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio); } }; - ALCcontext* eax_al_context_{}; - EaxFxSlotIndexValue eax_fx_slot_index_{}; - int eax_version_{}; // Current EAX version. - EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. - EaxEffectUPtr eax_effect_{}; - Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. - Eax4State eax4_{}; // EAX4 state. - Eax5State eax5_{}; // EAX5 state. - Eax5Props eax_{}; // Current EAX state. + ALCcontext* 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(const char* message); [[noreturn]] static void eax_fail_unknown_effect_id(); @@ -255,12 +259,14 @@ private: // validates it, // sets a dirty flag only if the new value differs form the old one, // and assigns the new value. - template - static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags) + template + static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, + std::bitset& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); - dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{}); + if(dst != src) + dirty_flags.set(DirtyBit); dst = src; } @@ -268,18 +274,18 @@ private: // validates it, // sets a dirty flag without comparing the values, // and assigns the new value. - template + template static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst, - EaxDirtyFlags& dirty_flags) + std::bitset& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); - dirty_flags |= TDirtyBit; + dirty_flags.set(DirtyBit); dst = src; } [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool - { return eax_fx_slot_index_ < 2; } + { return mEaxFXSlotIndex < 2; } void eax4_fx_slot_ensure_unlocked() const; @@ -287,15 +293,15 @@ private: [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; - void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; - void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; - void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept; - void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept; + void eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) noexcept; + void eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) noexcept; + void eax4_fx_slot_set_current_defaults(const EAX40FXSLOTPROPERTIES& props) noexcept; + void eax5_fx_slot_set_current_defaults(const EAX50FXSLOTPROPERTIES& props) noexcept; void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_defaults(); - static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); - static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); + 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. bool eax_get(const EaxCall& call); @@ -320,25 +326,25 @@ private: bool eax_set(const EaxCall& call); template< - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> - void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df, + void eax_fx_slot_commit_property(TState& state, std::bitset& dst_df, TMemberResult TProps::*member) noexcept { auto& src_i = state.i; - auto& dst_i = eax_; + auto& dst_i = mEax; - if((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) + if(mEaxDf.test(DirtyBit)) { - dst_df |= TDirtyBit; + dst_df.set(DirtyBit); dst_i.*member = src_i.*member; } } - void eax4_fx_slot_commit(EaxDirtyFlags& dst_df); - void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df); + 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 &effect); @@ -359,7 +365,7 @@ public: void UpdateAllEffectSlotProps(ALCcontext *context); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX using EaxAlEffectSlotUPtr = std::unique_ptr; EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); diff --git a/libs/openal-soft/al/buffer.cpp b/libs/openal-soft/al/buffer.cpp index 883878b2..2450c702 100644 --- a/libs/openal-soft/al/buffer.cpp +++ b/libs/openal-soft/al/buffer.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -53,15 +52,15 @@ #include "alnumeric.h" #include "alspan.h" #include "core/device.h" +#include "core/except.h" #include "core/logging.h" #include "core/resampler_limits.h" #include "core/voice.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include #include "eax/globals.h" @@ -89,7 +88,8 @@ constexpr auto EnumFromAmbiLayout(AmbiLayout layout) -> ALenum case AmbiLayout::FuMa: return AL_FUMA_SOFT; case AmbiLayout::ACN: return AL_ACN_SOFT; } - throw std::runtime_error{"Invalid AmbiLayout: "+std::to_string(int(layout))}; + throw std::runtime_error{fmt::format("Invalid AmbiLayout: {}", + int{al::to_underlying(layout)})}; } constexpr auto AmbiScalingFromEnum(ALenum scale) noexcept -> std::optional @@ -111,10 +111,11 @@ constexpr auto EnumFromAmbiScaling(AmbiScaling scale) -> ALenum case AmbiScaling::N3D: return AL_N3D_SOFT; case AmbiScaling::UHJ: break; } - throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; + throw std::runtime_error{fmt::format("Invalid AmbiScaling: {}", + int{al::to_underlying(scale)})}; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) @@ -133,12 +134,13 @@ constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum case EaxStorage::Accessible: return AL_STORAGE_ACCESSIBLE; case EaxStorage::Hardware: return AL_STORAGE_HARDWARE; } - throw std::runtime_error{"Invalid EaxStorage: "+std::to_string(int(storage))}; + throw std::runtime_error{fmt::format("Invalid EaxStorage: {}", + int{al::to_underlying(storage)})}; } -bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer, - const ALuint newsize) noexcept +auto eax_x_ram_check_availability(const al::Device &device, const ALbuffer &buffer, + const ALuint newsize) noexcept -> bool { ALuint freemem{device.eax_x_ram_free_size}; /* If the buffer is currently in "hardware", add its memory to the free @@ -149,7 +151,7 @@ bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffe return freemem >= newsize; } -void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept +void eax_x_ram_apply(al::Device &device, ALbuffer &buffer) noexcept { if(buffer.eax_x_ram_is_hardware) return; @@ -161,7 +163,7 @@ void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept } } -void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) noexcept +void eax_x_ram_clear(al::Device& al_device, ALbuffer& al_buffer) noexcept { if(al_buffer.eax_x_ram_is_hardware) al_device.eax_x_ram_free_size += al_buffer.OriginalSize; @@ -177,7 +179,8 @@ constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_M AL_MAP_PERSISTENT_BIT_SOFT)}; -auto EnsureBuffers(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureBuffers(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t @@ -200,7 +203,8 @@ catch(...) { return false; } -ALbuffer *AllocBuffer(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocBuffer(al::Device *device) noexcept -> ALbuffer* { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool @@ -219,9 +223,9 @@ ALbuffer *AllocBuffer(ALCdevice *device) noexcept return buffer; } -void FreeBuffer(ALCdevice *device, ALbuffer *buffer) +void FreeBuffer(al::Device *device, ALbuffer *buffer) { -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX @@ -236,7 +240,8 @@ void FreeBuffer(ALCdevice *device, ALbuffer *buffer) device->BufferList[lidx].FreeMask |= 1_u64 << slidx; } -inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* +[[nodiscard]] +auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -249,7 +254,7 @@ inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* return al::to_address(sublist.Buffers->begin() + slidx); } - +[[nodiscard]] constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) @@ -270,47 +275,58 @@ constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint if(type == FmtIMA4) { /* IMA4 block alignment must be a multiple of 8, plus 1. */ - if((align&7) == 1) return static_cast(align); + 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 static_cast(align); + if((align&1) == 0) return align; return 0; } - return static_cast(align); + return align; } /** Loads the specified data into the buffer, using the specified format. */ -void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, ALuint size, - const FmtChannels DstChannels, const FmtType DstType, const std::byte *SrcData, +void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, + const FmtChannels DstChannels, const FmtType DstType, const al::span SrcData, ALbitfieldSOFT access) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", + ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; + if(ambiorder > 3) + { + if(ALBuf->mAmbiLayout == AmbiLayout::FuMa) + context->throw_error(AL_INVALID_OPERATION, + "Cannot load {}{} order B-Format data with FuMa layout", ALBuf->mAmbiOrder, + GetCounterSuffix(ALBuf->mAmbiOrder)); + if(ALBuf->mAmbiScaling == AmbiScaling::FuMa) + context->throw_error(AL_INVALID_OPERATION, + "Cannot load {}{} order B-Format data with FuMa scaling", ALBuf->mAmbiOrder, + GetCounterSuffix(ALBuf->mAmbiOrder)); + } if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched format"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched format"); if(ALBuf->mBlockAlign != align) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched alignment"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); if(ALBuf->mAmbiOrder != ambiorder) - throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched order"}; + context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched order"); } /* Convert the size in bytes to blocks using the unpack block alignment. */ @@ -320,27 +336,27 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((size%BlockSize) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Data size %d is not a multiple of frame size %d (%d unpack alignment)", - size, BlockSize, align}; + context->throw_error(AL_INVALID_VALUE, + "Data size {} is not a multiple of frame size {} ({} unpack alignment)", + size, BlockSize, align); const ALuint blocks{size / BlockSize}; if(blocks > std::numeric_limits::max()/align) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); const size_t newsize{static_cast(blocks) * BlockSize}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { - ALCdevice &device = *context->mALDevice; + auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size}; + context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", + device.eax_x_ram_free_size, size); } #endif @@ -361,12 +377,12 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre newdata.swap(ALBuf->mDataStorage); } ALBuf->mData = ALBuf->mDataStorage; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif - if(SrcData != nullptr && !ALBuf->mData.empty()) - std::copy_n(SrcData, blocks*BlockSize, ALBuf->mData.begin()); + if(!SrcData.empty() && !ALBuf->mData.empty()) + std::copy_n(SrcData.begin(), blocks*BlockSize, ALBuf->mData.begin()); ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->OriginalSize = size; @@ -385,20 +401,20 @@ void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei fre ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled && ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } /** Prepares the buffer to use the specified callback, using the specified format. */ -void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, +void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying callback for in-use buffer {}", + ALBuf->id); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -406,8 +422,8 @@ void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsi const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : @@ -427,7 +443,7 @@ void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsi BufferVectorType(line_blocks*BlockSize).swap(ALBuf->mDataStorage); ALBuf->mData = ALBuf->mDataStorage; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif @@ -453,14 +469,14 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id}; + context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", + ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", + unpackalign, NameFromFormat(DstType)); auto get_type_alignment = [](const FmtType type) noexcept -> ALuint { @@ -483,8 +499,8 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz }; const auto typealign = get_type_alignment(DstType); if((reinterpret_cast(sdata) & (typealign-1)) != 0) - throw al::context_error{AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", - static_cast(sdata), NameFromFormat(DstType), typealign}; + context->throw_error(AL_INVALID_VALUE, "Pointer {} is misaligned for {} samples ({})", + static_cast(sdata), NameFromFormat(DstType), typealign); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -496,33 +512,32 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((sdatalen%BlockSize) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Data size %u is not a multiple of frame size %u (%u unpack alignment)", - sdatalen, BlockSize, align}; + context->throw_error(AL_INVALID_VALUE, + "Data size {} is not a multiple of frame size {} ({} unpack alignment)", + sdatalen, BlockSize, align); const ALuint blocks{sdatalen / BlockSize}; if(blocks > std::numeric_limits::max()/align) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) - throw al::context_error{AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; + context->throw_error(AL_OUT_OF_MEMORY, + "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { - ALCdevice &device = *context->mALDevice; + auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen)) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, - sdatalen}; + context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", + device.eax_x_ram_free_size, sdatalen); } #endif decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); - ALBuf->mData = {static_cast(sdata), sdatalen}; + ALBuf->mData = al::span{sdata, sdatalen}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif @@ -542,7 +557,7 @@ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsiz ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif @@ -617,11 +632,13 @@ auto DecomposeUserFormat(ALenum format) noexcept -> std::optional 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}}, @@ -663,21 +680,23 @@ AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d buffers", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} buffers", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; const al::span bids{buffers, static_cast(n)}; if(!EnsureBuffers(device, bids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} buffer{}", n, + (n==1) ? "" : "s"); std::generate(bids.begin(), bids.end(), [device]{ return AllocBuffer(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) @@ -685,21 +704,21 @@ FORCE_ALIGN void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei const ALuint *buffers) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d buffers", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} buffers", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ - auto validate_buffer = [device](const ALuint bid) + auto validate_buffer = [context,device](const ALuint bid) { if(!bid) return; ALbuffer *ALBuf{LookupBuffer(device, bid)}; if(!ALBuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bid); if(ALBuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid}; + context->throw_error(AL_INVALID_OPERATION, "Deleting in-use buffer {}", bid); }; const al::span bids{buffers, static_cast(n)}; @@ -713,15 +732,17 @@ try { }; std::for_each(bids.begin(), bids.end(), delete_buffer); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) FORCE_ALIGN ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(!buffer || LookupBuffer(device, buffer)) return AL_TRUE; return AL_FALSE; @@ -742,96 +763,101 @@ AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, c FORCE_ALIGN void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if((flags&INVALID_STORAGE_MASK) != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK}; + 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)) - throw al::context_error{AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"}; + context->throw_error(AL_INVALID_VALUE, + "Declaring persistently mapped storage without read or write access"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); + auto bdata = static_cast(data); LoadData(context, albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, - static_cast(data), flags); + al::span{bdata, bdata ? static_cast(size) : 0u}, flags); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) FORCE_ALIGN void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareUserPtr(context, albuf, freq, usrfmt->channels, usrfmt->type, static_cast(data), static_cast(size)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) FORCE_ALIGN void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if((access&INVALID_MAP_FLAGS) != 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid map flags 0x%x", - access&INVALID_MAP_FLAGS}; + context->throw_error(AL_INVALID_VALUE, "Invalid map flags {:#x}", + access&INVALID_MAP_FLAGS); if(!(access&MAP_READ_WRITE_FLAGS)) - throw al::context_error{AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} without read or write access", + buffer); const ALbitfieldSOFT unavailable{(albuf->Access^access) & access}; if(albuf->ref.load(std::memory_order_relaxed) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Mapping in-use buffer {} without persistent mapping", buffer); if(albuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Mapping already-mapped buffer {}", buffer); if((unavailable&AL_MAP_READ_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} for reading without read access", + buffer); if((unavailable&AL_MAP_WRITE_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer}; + context->throw_error(AL_INVALID_VALUE, + "Mapping buffer {} for writing without write access", buffer); if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer}; + context->throw_error(AL_INVALID_VALUE, + "Mapping buffer {} persistently without persistent access", buffer); if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize || static_cast(length) > albuf->OriginalSize - static_cast(offset)) - throw al::context_error{AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", - offset, length, buffer}; + context->throw_error(AL_INVALID_VALUE, "Mapping invalid range {}+{} for buffer {}", offset, + length, buffer); void *retval{albuf->mData.data() + offset}; albuf->MappedAccess = access; @@ -839,49 +865,54 @@ try { albuf->MappedSize = length; return retval; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { + return nullptr; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return nullptr; } AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) FORCE_ALIGN void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(albuf->MappedAccess == 0) - throw al::context_error{AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Unmapping unmapped buffer {}", buffer); albuf->MappedAccess = 0; albuf->MappedOffset = 0; albuf->MappedSize = 0; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Flushing buffer %u while not mapped for writing", buffer}; + context->throw_error(AL_INVALID_OPERATION, + "Flushing buffer {} while not mapped for writing", buffer); if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize || length > albuf->MappedOffset+albuf->MappedSize-offset) - throw al::context_error{AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", - offset, length, buffer}; + 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. @@ -890,41 +921,41 @@ try { */ std::atomic_thread_fence(std::memory_order_seq_cst); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); const ALuint unpack_align{albuf->UnpackAlign}; const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; if(align < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {}", unpack_align); if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) - throw al::context_error{AL_INVALID_ENUM, "Unpacking data with mismatched format"}; + context->throw_error(AL_INVALID_ENUM, "Unpacking data with mismatched format"); if(align != albuf->mBlockAlign) - throw al::context_error{AL_INVALID_VALUE, - "Unpacking data with alignment %u does not match original alignment %u", align, - albuf->mBlockAlign}; + context->throw_error(AL_INVALID_VALUE, + "Unpacking data with alignment {} does not match original alignment {}", align, + albuf->mBlockAlign); if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) - throw al::context_error{AL_INVALID_VALUE, - "Unpacking data with mismatched ambisonic order"}; + context->throw_error(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); if(albuf->MappedAccess != 0) - throw al::context_error{AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", - buffer}; + context->throw_error(AL_INVALID_OPERATION, "Unpacking data into mapped buffer {}", buffer); const ALuint num_chans{albuf->channelsFromFmt()}; const ALuint byte_align{ @@ -934,21 +965,23 @@ try { if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) - throw al::context_error{AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer}; + context->throw_error(AL_INVALID_VALUE, "Invalid data sub-range {}+{} on buffer {}", offset, + length, buffer); if((static_cast(offset)%byte_align) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align}; + context->throw_error(AL_INVALID_VALUE, + "Sub-range offset {} is not a multiple of frame size {} ({} unpack alignment)", + offset, byte_align, align); if((static_cast(length)%byte_align) != 0) - throw al::context_error{AL_INVALID_VALUE, - "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align}; + context->throw_error(AL_INVALID_VALUE, + "Sub-range length {} is not a multiple of frame size {} ({} unpack alignment)", + length, byte_align, align); std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -956,19 +989,19 @@ AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -976,40 +1009,40 @@ FORCE_ALIGN void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer ALfloat value1 [[maybe_unused]], ALfloat value2 [[maybe_unused]], ALfloat value3 [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1017,79 +1050,92 @@ AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack block alignment %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack block alignment {}", value); albuf->UnpackAlign = static_cast(value); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid pack block alignment %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid pack block alignment {}", value); albuf->PackAlign = static_cast(value); return; case AL_AMBISONIC_LAYOUT_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's ambisonic layout", buffer}; + 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; } - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic layout {:#04x}", + as_unsigned(value)); case AL_AMBISONIC_SCALING_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's ambisonic scaling", buffer}; + 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; } - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value}; + 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) - throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic order {}", value); albuf->UnpackAmbiOrder = static_cast(value); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) FORCE_ALIGN void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1 [[maybe_unused]], ALint value2 [[maybe_unused]], ALint value3 [[maybe_unused]]) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) @@ -1097,7 +1143,7 @@ FORCE_ALIGN void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer const ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1110,34 +1156,36 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_LOOP_POINTS_SOFT: auto vals = al::span{values, 2_uz}; if(albuf->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying in-use buffer %u's loop points", buffer}; + context->throw_error(AL_INVALID_OPERATION, "Modifying in-use buffer {}'s loop points", + buffer); if(vals[0] < 0 || vals[0] >= vals[1] || static_cast(vals[1]) > albuf->mSampleLen) - throw al::context_error{AL_INVALID_VALUE, - "Invalid loop point range %d -> %d on buffer %u", vals[0], vals[1], buffer}; + context->throw_error(AL_INVALID_VALUE, + "Invalid loop point range {} -> {} on buffer {}", vals[0], vals[1], buffer); albuf->mLoopStart = static_cast(vals[0]); albuf->mLoopEnd = static_cast(vals[1]); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1145,14 +1193,14 @@ AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,valu FORCE_ALIGN void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1162,31 +1210,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) @@ -1200,21 +1251,21 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1222,14 +1273,14 @@ AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1280,31 +1331,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) @@ -1329,14 +1383,14 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1347,11 +1401,13 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1359,39 +1415,41 @@ AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, FORCE_ALIGN void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(freq < 1) - throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if(callback == nullptr) - throw al::context_error{AL_INVALID_VALUE, "NULL callback"}; + context->throw_error(AL_INVALID_VALUE, "NULL callback"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) - throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareCallback(context, albuf, freq, usrfmt->channels, usrfmt->type, callback, userptr); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) FORCE_ALIGN void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -1403,31 +1461,34 @@ try { return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-pointer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) @@ -1442,22 +1503,21 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); - switch(param) - { - } - throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -1501,12 +1561,12 @@ AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) no void ALbuffer::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; auto buffer = LookupBuffer(device, id); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); device->mBufferNames.insert_or_assign(id, name); } @@ -1530,25 +1590,25 @@ BufferSubList::~BufferSubList() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) noexcept try { if(!eax_g_is_enabled) - throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled"}; + context->throw_error(AL_INVALID_OPERATION, "EAX not enabled"); const auto storage = EaxStorageFromEnum(value); if(!storage) - throw al::context_error{AL_INVALID_ENUM, "Unsupported X-RAM mode 0x%x", value}; + context->throw_error(AL_INVALID_ENUM, "Unsupported X-RAM mode {:#x}", as_unsigned(value)); if(n == 0) return AL_TRUE; if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Buffer count %d out of range", n}; + context->throw_error(AL_INVALID_VALUE, "Buffer count {} out of range", n); if(!buffers) - throw al::context_error{AL_INVALID_VALUE, "Null AL buffers"}; + context->throw_error(AL_INVALID_VALUE, "Null AL buffers"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; @@ -1562,7 +1622,7 @@ try { const auto buffer = LookupBuffer(device, bufid); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? @@ -1572,9 +1632,9 @@ try { { if(!buffer->eax_x_ram_is_hardware && buffer->OriginalSize > device->eax_x_ram_free_size) - throw al::context_error{AL_OUT_OF_MEMORY, - "Out of X-RAM memory (need: %u, avail: %u)", buffer->OriginalSize, - device->eax_x_ram_free_size}; + context->throw_error(AL_OUT_OF_MEMORY, + "Out of X-RAM memory (need: {}, avail: {})", buffer->OriginalSize, + device->eax_x_ram_free_size); eax_x_ram_apply(*device, *buffer); } @@ -1593,7 +1653,7 @@ try { const auto buffer = LookupBuffer(device, bufid); if(!buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? @@ -1610,15 +1670,15 @@ try { if(!buffer->eax_x_ram_is_hardware) { if(std::numeric_limits::max() - buffer->OriginalSize < total_needed) - throw al::context_error{AL_OUT_OF_MEMORY, "Size overflow (%u + %zu)", - buffer->OriginalSize, total_needed}; + context->throw_error(AL_OUT_OF_MEMORY, "Size overflow ({} + {})", + buffer->OriginalSize, total_needed); total_needed += buffer->OriginalSize; } } if(total_needed > device->eax_x_ram_free_size) - throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: %zu, avail: %u)", - 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. */ @@ -1633,8 +1693,11 @@ try { return AL_TRUE; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "[EAXSetBufferMode] %s", e.what()); +catch(al::base_exception&) { + return AL_FALSE; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return AL_FALSE; } @@ -1642,24 +1705,26 @@ FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) noexcept try { - if(!eax_g_is_enabled) - throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled."}; + context->throw_error(AL_INVALID_OPERATION, "EAX not enabled."); if(pReserved) - throw al::context_error{AL_INVALID_VALUE, "Non-null reserved parameter"}; + context->throw_error(AL_INVALID_VALUE, "Non-null reserved parameter"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; const auto al_buffer = LookupBuffer(device, buffer); if(!al_buffer) - throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "[EAXGetBufferMode] %s", e.what()); +catch(al::base_exception&) { + return AL_NONE; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return AL_NONE; } diff --git a/libs/openal-soft/al/buffer.h b/libs/openal-soft/al/buffer.h index f7661ec8..187bc6bd 100644 --- a/libs/openal-soft/al/buffer.h +++ b/libs/openal-soft/al/buffer.h @@ -1,6 +1,8 @@ #ifndef AL_BUFFER_H #define AL_BUFFER_H +#include "config.h" + #include #include #include @@ -17,7 +19,7 @@ #include "core/buffer_storage.h" #include "vector.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX enum class EaxStorage : uint8_t { Automatic, Accessible, @@ -54,7 +56,7 @@ struct ALbuffer : public BufferStorage { DISABLE_ALLOC -#ifdef ALSOFT_EAX +#if ALSOFT_EAX EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; bool eax_x_ram_is_hardware{}; #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/debug.cpp b/libs/openal-soft/al/debug.cpp index 39318144..bb742e74 100644 --- a/libs/openal-soft/al/debug.cpp +++ b/libs/openal-soft/al/debug.cpp @@ -21,18 +21,17 @@ #include "alc/context.h" #include "alc/device.h" -#include "alc/inprogext.h" #include "alnumeric.h" #include "alspan.h" -#include "alstring.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 "error.h" #include "filter.h" +#include "fmt/core.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "source.h" @@ -45,6 +44,8 @@ DebugGroup::~DebugGroup() = default; namespace { +using namespace std::string_view_literals; + static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); template @@ -109,7 +110,8 @@ constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; } - throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))}; + throw std::runtime_error{fmt::format("Unexpected debug source value: {}", + int{al::to_underlying(source)})}; } constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum @@ -126,7 +128,8 @@ constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; } - throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))}; + throw std::runtime_error{fmt::format("Unexpected debug type value: {}", + int{al::to_underlying(type)})}; } constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum @@ -138,50 +141,51 @@ constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; } - throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))}; + throw std::runtime_error{fmt::format("Unexpected debug severity value: {}", + int{al::to_underlying(severity)})}; } -constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char* +constexpr auto GetDebugSourceName(DebugSource source) noexcept -> std::string_view { switch(source) { - case DebugSource::API: return "API"; - case DebugSource::System: return "Audio System"; - case DebugSource::ThirdParty: return "Third Party"; - case DebugSource::Application: return "Application"; - case DebugSource::Other: return "Other"; + 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 ""; + return ""sv; } -constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char* +constexpr auto GetDebugTypeName(DebugType type) noexcept -> std::string_view { switch(type) { - case DebugType::Error: return "Error"; - case DebugType::DeprecatedBehavior: return "Deprecated Behavior"; - case DebugType::UndefinedBehavior: return "Undefined Behavior"; - case DebugType::Portability: return "Portability"; - case DebugType::Performance: return "Performance"; - case DebugType::Marker: return "Marker"; - case DebugType::PushGroup: return "Push Group"; - case DebugType::PopGroup: return "Pop Group"; - case DebugType::Other: return "Other"; + 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 ""; + return ""sv; } -constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char* +constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> std::string_view { switch(severity) { - case DebugSeverity::High: return "High"; - case DebugSeverity::Medium: return "Medium"; - case DebugSeverity::Low: return "Low"; - case DebugSeverity::Notification: return "Notification"; + 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 ""; + return ""sv; } } // namespace @@ -195,8 +199,8 @@ void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, Debug if(message.length() >= MaxDebugMessageLength) UNLIKELY { - ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(), - MaxDebugMessageLength, al::sizei(message), message.data()); + ERR("Debug message too long ({} >= {}):\n-> {}", message.length(), + MaxDebugMessageLength, message); return; } @@ -231,13 +235,13 @@ void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, Debug mDebugLog.emplace_back(source, type, id, severity, message); else UNLIKELY ERR("Debug message log overflow. Lost message:\n" - " Source: %s\n" - " Type: %s\n" - " ID: %u\n" - " Severity: %s\n" - " Message: \"%.*s\"\n", + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, - GetDebugSeverityName(severity), al::sizei(message), message.data()); + GetDebugSeverityName(severity), message); } } @@ -260,32 +264,36 @@ try { return; if(!message) - throw al::context_error{AL_INVALID_VALUE, "Null message pointer"}; + context->throw_error(AL_INVALID_VALUE, "Null message pointer"); auto msgview = (length < 0) ? std::string_view{message} : std::string_view{message, static_cast(length)}; if(msgview.size() >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", - msgview.size(), MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(), + MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) - throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", + as_unsigned(source)); auto dtype = GetDebugType(type); if(!dtype) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); auto dseverity = GetDebugSeverity(severity); if(!dseverity) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", + as_unsigned(severity)); context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -296,20 +304,20 @@ try { if(count > 0) { if(!ids) - throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"}; + context->throw_error(AL_INVALID_VALUE, "IDs is null with non-0 count"); if(source == AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug source cannot be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug source cannot be AL_DONT_CARE_EXT with IDs"); if(type == AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug type cannot be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug type cannot be AL_DONT_CARE_EXT with IDs"); if(severity != AL_DONT_CARE_EXT) - throw al::context_error{AL_INVALID_OPERATION, - "Debug severity must be AL_DONT_CARE_EXT with IDs"}; + context->throw_error(AL_INVALID_OPERATION, + "Debug severity must be AL_DONT_CARE_EXT with IDs"); } if(enable != AL_TRUE && enable != AL_FALSE) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug enable {}", enable); static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; static constexpr auto Values = make_array_sequence(); @@ -319,7 +327,8 @@ try { { auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", + as_unsigned(source)); srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); } @@ -328,7 +337,7 @@ try { { auto dtype = GetDebugType(type); if(!dtype) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); } @@ -337,7 +346,8 @@ try { { auto dseverity = GetDebugSeverity(severity); if(!dseverity) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", + as_unsigned(severity)); svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); } @@ -383,8 +393,10 @@ try { [apply_type](const uint idx){ apply_type(1<setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -396,23 +408,24 @@ try { { size_t newlen{std::strlen(message)}; if(newlen >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen, - MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen, + MaxDebugMessageLength); length = static_cast(newlen); } else if(length >= MaxDebugMessageLength) - throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length, - MaxDebugMessageLength}; + context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length, + MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) - throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) - throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", + as_unsigned(source)); std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() >= MaxDebugGroupDepth) - throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"}; + context->throw_error(AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"); context->mDebugGroups.emplace_back(*dsource, id, std::string_view{message, static_cast(length)}); @@ -426,8 +439,10 @@ try { context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, DebugSeverity::Notification, newback.mMessage); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) @@ -435,8 +450,7 @@ FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexc try { std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() <= 1) - throw al::context_error{AL_STACK_UNDERFLOW_EXT, - "Attempting to pop the default debug group"}; + context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group"); DebugGroup &debug = context->mDebugGroups.back(); const auto source = debug.mSource; @@ -448,8 +462,10 @@ try { context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, DebugSeverity::Notification, message); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -458,66 +474,60 @@ FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) noexcept try { - if(logBufSize < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"}; + if(logBuf && logBufSize < 0) + context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size"); - auto sourcesOut = al::span{sources, sources ? count : 0u}; - auto typesOut = al::span{types, types ? count : 0u}; - auto idsOut = al::span{ids, ids ? count : 0u}; - auto severitiesOut = al::span{severities, severities ? count : 0u}; - auto lengthsOut = al::span{lengths, lengths ? count : 0u}; - auto logOut = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; + const auto sourcesSpan = al::span{sources, sources ? count : 0u}; + const auto typesSpan = al::span{types, types ? count : 0u}; + const auto idsSpan = al::span{ids, ids ? count : 0u}; + const auto severitiesSpan = al::span{severities, severities ? count : 0u}; + const auto lengthsSpan = al::span{lengths, lengths ? count : 0u}; + const auto logSpan = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; - std::lock_guard debuglock{context->mDebugCbLock}; + auto sourceiter = sourcesSpan.begin(); + auto typeiter = typesSpan.begin(); + auto iditer = idsSpan.begin(); + auto severityiter = severitiesSpan.begin(); + auto lengthiter = lengthsSpan.begin(); + auto logiter = logSpan.begin(); + + auto debuglock = std::lock_guard{context->mDebugCbLock}; for(ALuint i{0};i < count;++i) { if(context->mDebugLog.empty()) return i; auto &entry = context->mDebugLog.front(); - const size_t tocopy{entry.mMessage.size() + 1}; - if(logOut.data() != nullptr) + const auto tocopy = size_t{entry.mMessage.size() + 1}; + if(al::to_address(logiter) != nullptr) { - if(logOut.size() < tocopy) + if(static_cast(std::distance(logiter, logSpan.end())) < tocopy) return i; - auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin()); - *oiter = '\0'; - logOut = {oiter+1, logOut.end()}; + logiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logiter); + *(logiter++) = '\0'; } - if(!sourcesOut.empty()) - { - sourcesOut.front() = GetDebugSourceEnum(entry.mSource); - sourcesOut = sourcesOut.subspan<1>(); - } - if(!typesOut.empty()) - { - typesOut.front() = GetDebugTypeEnum(entry.mType); - typesOut = typesOut.subspan<1>(); - } - if(!idsOut.empty()) - { - idsOut.front() = entry.mId; - idsOut = idsOut.subspan<1>(); - } - if(!severitiesOut.empty()) - { - severitiesOut.front() = GetDebugSeverityEnum(entry.mSeverity); - severitiesOut = severitiesOut.subspan<1>(); - } - if(!lengthsOut.empty()) - { - lengthsOut.front() = static_cast(tocopy); - lengthsOut = lengthsOut.subspan<1>(); - } + if(al::to_address(sourceiter) != nullptr) + *(sourceiter++) = GetDebugSourceEnum(entry.mSource); + if(al::to_address(typeiter) != nullptr) + *(typeiter++) = GetDebugTypeEnum(entry.mType); + if(al::to_address(iditer) != nullptr) + *(iditer++) = entry.mId; + if(al::to_address(severityiter) != nullptr) + *(severityiter++) = GetDebugSeverityEnum(entry.mSeverity); + if(al::to_address(lengthiter) != nullptr) + *(lengthiter++) = static_cast(tocopy); context->mDebugLog.pop_front(); } return count; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { + return 0; +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); return 0; } @@ -526,29 +536,30 @@ FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum ALuint name, ALsizei length, const ALchar *label) noexcept try { if(!label && length != 0) - throw al::context_error{AL_INVALID_VALUE, "Null label pointer"}; + context->throw_error(AL_INVALID_VALUE, "Null label pointer"); auto objname = (length < 0) ? std::string_view{label} : std::string_view{label, static_cast(length)}; if(objname.size() >= MaxObjectLabelLength) - throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)", - objname.size(), MaxObjectLabelLength}; + context->throw_error(AL_INVALID_VALUE, "Object label length too long ({} >= {})", + objname.size(), MaxObjectLabelLength); - if(identifier == AL_SOURCE_EXT) - return ALsource::SetName(context, name, objname); - if(identifier == AL_BUFFER) - return ALbuffer::SetName(context, name, objname); - if(identifier == AL_FILTER_EXT) - return ALfilter::SetName(context, name, objname); - if(identifier == AL_EFFECT_EXT) - return ALeffect::SetName(context, name, objname); - if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) - return ALeffectslot::SetName(context, name, objname); + switch(identifier) + { + case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return; + case AL_BUFFER: ALbuffer::SetName(context, name, objname); return; + case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return; + case AL_EFFECT_EXT: ALeffect::SetName(context, name, objname); return; + case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; + } - throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; + context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", + as_unsigned(identifier)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) @@ -556,12 +567,12 @@ FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALen ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept try { if(bufSize < 0) - throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; + context->throw_error(AL_INVALID_VALUE, "Negative label bufSize"); if(!label && !length) - throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; + context->throw_error(AL_INVALID_VALUE, "Null length and label"); if(label && bufSize == 0) - throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; + context->throw_error(AL_INVALID_VALUE, "Zero label bufSize"); const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; auto copy_name = [name,length,labelOut](std::unordered_map &names) @@ -591,20 +602,20 @@ try { } else if(identifier == AL_BUFFER) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard buflock{device->BufferLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->BufferLock}; copy_name(device->mBufferNames); } else if(identifier == AL_FILTER_EXT) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->FilterLock}; copy_name(device->mFilterNames); } else if(identifier == AL_EFFECT_EXT) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto buflock = std::lock_guard{device->EffectLock}; copy_name(device->mEffectNames); } else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) @@ -613,8 +624,11 @@ try { copy_name(context->mEffectSlotNames); } else - throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; + context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", + as_unsigned(identifier)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } diff --git a/libs/openal-soft/al/eax/api.h b/libs/openal-soft/al/eax/api.h index 3e621d30..925e7b4c 100644 --- a/libs/openal-soft/al/eax/api.h +++ b/libs/openal-soft/al/eax/api.h @@ -21,6 +21,8 @@ #include "AL/al.h" +#include "opthelpers.h" + #ifndef _WIN32 using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ @@ -37,14 +39,16 @@ inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } #endif // _WIN32 +/* TODO: This seems to create very inefficient comparisons. C++20 should allow + * creating default comparison operators, avoiding the need for this. + */ #define DECL_EQOP(T, ...) \ [[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ [[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ -{ return lhs.get_members() == rhs.get_members(); } \ -[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \ -{ return !(lhs == rhs); } +{ return lhs.get_members() == rhs.get_members(); } \ +[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept { return !(lhs == rhs); } -extern const GUID DSPROPSETID_EAX_ReverbProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX_ReverbProperties; enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int { DSPROPERTY_EAX_ALL, @@ -62,7 +66,7 @@ struct EAX_REVERBPROPERTIES { }; // EAX_REVERBPROPERTIES -extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int { DSPROPERTY_EAXBUFFER_ALL, @@ -78,7 +82,7 @@ constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; -extern const GUID DSPROPSETID_EAX20_ListenerProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_ListenerProperties; enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int { DSPROPERTY_EAX20LISTENER_NONE, @@ -216,7 +220,7 @@ constexpr auto EAX2LISTENER_DEFAULTFLAGS = EAX2LISTENERFLAGS_DECAYHFLIMIT; -extern const GUID DSPROPSETID_EAX20_BufferProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_BufferProperties; enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int { DSPROPERTY_EAX20BUFFER_NONE, @@ -252,9 +256,9 @@ struct EAX20BUFFERPROPERTIES { unsigned long dwFlags; // modifies the behavior of properties }; // EAX20BUFFERPROPERTIES -extern const GUID DSPROPSETID_EAX30_ListenerProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_ListenerProperties; -extern const GUID DSPROPSETID_EAX30_BufferProperties; +DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_BufferProperties; constexpr auto EAX_MAX_FXSLOTS = 4; @@ -272,31 +276,29 @@ constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L; constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; -extern const GUID EAX_NULL_GUID; +DECL_HIDDEN extern const GUID EAX_NULL_GUID; -extern const GUID EAX_PrimaryFXSlotID; +DECL_HIDDEN extern const GUID EAX_PrimaryFXSlotID; struct EAXVECTOR { float x; float y; float z; - [[nodiscard]] - auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); } -}; // EAXVECTOR +}; [[nodiscard]] inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept -{ return lhs.get_members() == rhs.get_members(); } +{ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; } [[nodiscard]] inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return !(lhs == rhs); } -extern const GUID EAXPROPERTYID_EAX40_Context; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Context; -extern const GUID EAXPROPERTYID_EAX50_Context; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Context; // EAX50 constexpr auto HEADPHONES = 0UL; @@ -372,17 +374,17 @@ constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; -extern const GUID EAXPROPERTYID_EAX40_FXSlot0; -extern const GUID EAXPROPERTYID_EAX50_FXSlot0; -extern const GUID EAXPROPERTYID_EAX40_FXSlot1; -extern const GUID EAXPROPERTYID_EAX50_FXSlot1; -extern const GUID EAXPROPERTYID_EAX40_FXSlot2; -extern const GUID EAXPROPERTYID_EAX50_FXSlot2; -extern const GUID EAXPROPERTYID_EAX40_FXSlot3; -extern const GUID EAXPROPERTYID_EAX50_FXSlot3; +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; -extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; -extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; +DECL_HIDDEN extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; +DECL_HIDDEN extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; enum EAXFXSLOT_PROPERTY : unsigned int { EAXFXSLOT_PARAMETER = 0, @@ -445,8 +447,8 @@ struct EAX50FXSLOTPROPERTIES : public EAX40FXSLOTPROPERTIES { float flOcclusionLFRatio; }; // EAX50FXSLOTPROPERTIES -extern const GUID EAXPROPERTYID_EAX40_Source; -extern const GUID EAXPROPERTYID_EAX50_Source; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Source; +DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Source; // Source object properties enum EAXSOURCE_PROPERTY : unsigned int { @@ -713,16 +715,16 @@ struct EAXSOURCEEXCLUSIONSENDPROPERTIES { float flExclusionLFRatio; }; // EAXSOURCEEXCLUSIONSENDPROPERTIES -extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; -extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; -extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; +DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; // EAX Reverb Effect -extern const GUID EAX_REVERB_EFFECT; +DECL_HIDDEN extern const GUID EAX_REVERB_EFFECT; // Reverb effect properties enum EAXREVERB_PROPERTY : unsigned int { @@ -959,18 +961,18 @@ constexpr auto EAXREVERB_DEFAULTFLAGS = using Eax1ReverbPresets = std::array; -extern const Eax1ReverbPresets EAX1REVERB_PRESETS; +DECL_HIDDEN extern const Eax1ReverbPresets EAX1REVERB_PRESETS; using Eax2ReverbPresets = std::array; -extern const Eax2ReverbPresets EAX2REVERB_PRESETS; +DECL_HIDDEN extern const Eax2ReverbPresets EAX2REVERB_PRESETS; using EaxReverbPresets = std::array; -extern const EaxReverbPresets EAXREVERB_PRESETS; +DECL_HIDDEN extern const EaxReverbPresets EAXREVERB_PRESETS; // AGC Compressor Effect -extern const GUID EAX_AGCCOMPRESSOR_EFFECT; +DECL_HIDDEN extern const GUID EAX_AGCCOMPRESSOR_EFFECT; enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { EAXAGCCOMPRESSOR_NONE, @@ -991,7 +993,7 @@ constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; // Autowah Effect -extern const GUID EAX_AUTOWAH_EFFECT; +DECL_HIDDEN extern const GUID EAX_AUTOWAH_EFFECT; enum EAXAUTOWAH_PROPERTY : unsigned int { EAXAUTOWAH_NONE, @@ -1030,7 +1032,7 @@ constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; // Chorus Effect -extern const GUID EAX_CHORUS_EFFECT; +DECL_HIDDEN extern const GUID EAX_CHORUS_EFFECT; enum EAXCHORUS_PROPERTY : unsigned int { EAXCHORUS_NONE, @@ -1086,7 +1088,7 @@ constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; // Distortion Effect -extern const GUID EAX_DISTORTION_EFFECT; +DECL_HIDDEN extern const GUID EAX_DISTORTION_EFFECT; enum EAXDISTORTION_PROPERTY : unsigned int { EAXDISTORTION_NONE, @@ -1131,7 +1133,7 @@ constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; // Echo Effect -extern const GUID EAX_ECHO_EFFECT; +DECL_HIDDEN extern const GUID EAX_ECHO_EFFECT; enum EAXECHO_PROPERTY : unsigned int { EAXECHO_NONE, @@ -1176,7 +1178,7 @@ constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; // Equalizer Effect -extern const GUID EAX_EQUALIZER_EFFECT; +DECL_HIDDEN extern const GUID EAX_EQUALIZER_EFFECT; enum EAXEQUALIZER_PROPERTY : unsigned int { EAXEQUALIZER_NONE, @@ -1252,7 +1254,7 @@ constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; // Flanger Effect -extern const GUID EAX_FLANGER_EFFECT; +DECL_HIDDEN extern const GUID EAX_FLANGER_EFFECT; enum EAXFLANGER_PROPERTY : unsigned int { EAXFLANGER_NONE, @@ -1308,7 +1310,7 @@ constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; // Frequency Shifter Effect -extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; +DECL_HIDDEN extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int { EAXFREQUENCYSHIFTER_NONE, @@ -1347,7 +1349,7 @@ constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_M // Vocal Morpher Effect -extern const GUID EAX_VOCALMORPHER_EFFECT; +DECL_HIDDEN extern const GUID EAX_VOCALMORPHER_EFFECT; enum EAXVOCALMORPHER_PROPERTY : unsigned int { EAXVOCALMORPHER_NONE, @@ -1439,7 +1441,7 @@ constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; // Pitch Shifter Effect -extern const GUID EAX_PITCHSHIFTER_EFFECT; +DECL_HIDDEN extern const GUID EAX_PITCHSHIFTER_EFFECT; enum EAXPITCHSHIFTER_PROPERTY : unsigned int { EAXPITCHSHIFTER_NONE, @@ -1466,7 +1468,7 @@ constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; // Ring Modulator Effect -extern const GUID EAX_RINGMODULATOR_EFFECT; +DECL_HIDDEN extern const GUID EAX_RINGMODULATOR_EFFECT; enum EAXRINGMODULATOR_PROPERTY : unsigned int { EAXRINGMODULATOR_NONE, diff --git a/libs/openal-soft/al/eax/call.cpp b/libs/openal-soft/al/eax/call.cpp index 013a3992..5c69d2e5 100644 --- a/libs/openal-soft/al/eax/call.cpp +++ b/libs/openal-soft/al/eax/call.cpp @@ -15,13 +15,8 @@ public: } // namespace -EaxCall::EaxCall( - EaxCallType type, - const GUID& property_set_guid, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size) +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} @@ -145,23 +140,34 @@ EaxCall::EaxCall( fail("Unsupported property set id."); } - switch(mPropertyId) + if(mPropertySetId == EaxCallPropertySetId::context) { - case EAXCONTEXT_LASTERROR: - case EAXCONTEXT_SPEAKERCONFIG: - case EAXCONTEXT_EAXSESSION: - case EAXFXSLOT_NONE: - case EAXFXSLOT_ALLPARAMETERS: - case EAXFXSLOT_LOADEFFECT: - case EAXFXSLOT_VOLUME: - case EAXFXSLOT_LOCK: - case EAXFXSLOT_FLAGS: - case EAXFXSLOT_OCCLUSION: - case EAXFXSLOT_OCCLUSIONLFRATIO: - // 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; + 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) diff --git a/libs/openal-soft/al/eax/effect.h b/libs/openal-soft/al/eax/effect.h index 9b1f8bd3..eead5b81 100644 --- a/libs/openal-soft/al/eax/effect.h +++ b/libs/openal-soft/al/eax/effect.h @@ -1,17 +1,17 @@ #ifndef EAX_EFFECT_INCLUDED #define EAX_EFFECT_INCLUDED - #include #include #include -#include "alnumeric.h" #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."; } @@ -123,10 +123,6 @@ template struct EaxCommitter { struct Exception; - EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) - : mEaxProps{eaxprops}, mAlProps{alprops} - { } - EaxEffectProps &mEaxProps; EffectProps &mAlProps; @@ -141,10 +137,18 @@ struct EaxCommitter { [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } + +private: + EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) + : mEaxProps{eaxprops}, mAlProps{alprops} + { } + + friend T; }; struct EaxAutowahCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxAutowahCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAUTOWAHPROPERTIES &props); @@ -153,7 +157,8 @@ struct EaxAutowahCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); }; struct EaxChorusCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxChorusCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXCHORUSPROPERTIES &props); @@ -162,7 +167,8 @@ struct EaxChorusCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); }; struct EaxCompressorCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxCompressorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); @@ -171,7 +177,8 @@ struct EaxCompressorCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); }; struct EaxDistortionCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxDistortionCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXDISTORTIONPROPERTIES &props); @@ -180,7 +187,8 @@ struct EaxDistortionCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); }; struct EaxEchoCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxEchoCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXECHOPROPERTIES &props); @@ -189,7 +197,8 @@ struct EaxEchoCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); }; struct EaxEqualizerCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxEqualizerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXEQUALIZERPROPERTIES &props); @@ -198,7 +207,8 @@ struct EaxEqualizerCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); }; struct EaxFlangerCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxFlangerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFLANGERPROPERTIES &props); @@ -207,7 +217,8 @@ struct EaxFlangerCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); }; struct EaxFrequencyShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxFrequencyShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); @@ -216,7 +227,8 @@ struct EaxFrequencyShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxModulatorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXRINGMODULATORPROPERTIES &props); @@ -225,7 +237,8 @@ struct EaxModulatorCommitter : public EaxCommitter { static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); }; struct EaxPitchShifterCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxPitchShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXPITCHSHIFTERPROPERTIES &props); @@ -234,7 +247,8 @@ struct EaxPitchShifterCommitter : public EaxCommitter static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); }; struct EaxVocalMorpherCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxVocalMorpherCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXVOCALMORPHERPROPERTIES &props); @@ -243,7 +257,8 @@ struct EaxVocalMorpherCommitter : public EaxCommitter static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); }; struct EaxNullCommitter : public EaxCommitter { - using EaxCommitter::EaxCommitter; + template + explicit EaxNullCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const std::monostate &props); @@ -279,7 +294,7 @@ public: ~EaxEffect() = default; ALenum al_effect_type_{AL_EFFECT_NULL}; - EffectProps al_effect_props_{}; + EffectProps al_effect_props_; using Props1 = EAX_REVERBPROPERTIES; using Props2 = EAX20LISTENERPROPERTIES; @@ -308,7 +323,7 @@ public: int version_{}; bool changed_{}; - Props4 props_{}; + Props4 props_; State1 state1_{}; State2 state2_{}; State3 state3_{}; diff --git a/libs/openal-soft/al/eax/exception.h b/libs/openal-soft/al/eax/exception.h index 336654f0..64cf7c49 100644 --- a/libs/openal-soft/al/eax/exception.h +++ b/libs/openal-soft/al/eax/exception.h @@ -10,9 +10,14 @@ class EaxException : public std::runtime_error { static std::string make_message(std::string_view context, std::string_view message); public: + EaxException() = delete; + EaxException(const EaxException&) = default; + EaxException(EaxException&&) = default; EaxException(std::string_view context, std::string_view message); ~EaxException() override; -}; // EaxException + auto operator=(const EaxException&) -> EaxException& = default; + auto operator=(EaxException&&) -> EaxException& = default; +}; -#endif // !EAX_EXCEPTION_INCLUDED +#endif /* EAX_EXCEPTION_INCLUDED */ diff --git a/libs/openal-soft/al/eax/fx_slots.h b/libs/openal-soft/al/eax/fx_slots.h index b7ed1031..d2d90b24 100644 --- a/libs/openal-soft/al/eax/fx_slots.h +++ b/libs/openal-soft/al/eax/fx_slots.h @@ -6,13 +6,10 @@ #include "al/auxeffectslot.h" -#include "api.h" -#include "call.h" #include "fx_slot_index.h" -class EaxFxSlots -{ +class EaxFxSlots { public: void initialize(ALCcontext& al_context); diff --git a/libs/openal-soft/al/eax/utils.cpp b/libs/openal-soft/al/eax/utils.cpp index 871ab764..2eec4277 100644 --- a/libs/openal-soft/al/eax/utils.cpp +++ b/libs/openal-soft/al/eax/utils.cpp @@ -5,7 +5,6 @@ #include #include -#include "alstring.h" #include "core/logging.h" @@ -18,9 +17,9 @@ void eax_log_exception(std::string_view message) noexcept std::rethrow_exception(exception_ptr); } catch(const std::exception& ex) { - ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); + ERR("{} {}", message, ex.what()); } catch(...) { - ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); + ERR("{} {}", message, "Generic exception."); } } diff --git a/libs/openal-soft/al/eax/utils.h b/libs/openal-soft/al/eax/utils.h index 8e0f975f..fb0d8764 100644 --- a/libs/openal-soft/al/eax/utils.h +++ b/libs/openal-soft/al/eax/utils.h @@ -1,15 +1,11 @@ #ifndef EAX_UTILS_INCLUDED #define EAX_UTILS_INCLUDED -#include -#include -#include #include -#include +#include "fmt/core.h" #include "opthelpers.h" -using EaxDirtyFlags = unsigned int; struct EaxAlLowPassParam { float gain; @@ -25,71 +21,9 @@ void eax_validate_range(std::string_view value_name, const TValue& value, const if(value >= min_value && value <= max_value) LIKELY return; - const auto message = - std::string{value_name} + - " out of range (value: " + - std::to_string(value) + "; min: " + - std::to_string(min_value) + "; max: " + - std::to_string(max_value) + ")."; - + const auto message = fmt::format("{} out of range (value: {}; min: {}; max: {}).", value_name, + value, min_value, max_value); throw TException{message.c_str()}; } -namespace detail { - -template -struct EaxIsBitFieldStruct { -private: - using yes = std::true_type; - using no = std::false_type; - - template - static auto test(int) -> decltype(std::declval(), yes{}); - - template - static no test(...); - -public: - static constexpr auto value = std::is_same(0)), yes>::value; -}; - -template -inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept -{ - static_assert(sizeof(T) == sizeof(TValue), "Invalid type size."); - return reinterpret_cast(lhs) == reinterpret_cast(rhs); -} - -} // namespace detail - -template< - typename T, - std::enable_if_t::value, int> = 0 -> -inline bool operator==(const T& lhs, const T& rhs) noexcept -{ - using Value = std::conditional_t< - sizeof(T) == 1, - std::uint8_t, - std::conditional_t< - sizeof(T) == 2, - std::uint16_t, - std::conditional_t< - sizeof(T) == 4, - std::uint32_t, - void>>>; - - static_assert(!std::is_same::value, "Unsupported type."); - return detail::eax_bit_fields_are_equal(lhs, rhs); -} - -template< - typename T, - std::enable_if_t::value, int> = 0 -> -inline bool operator!=(const T& lhs, const T& rhs) noexcept -{ - return !(lhs == rhs); -} - #endif // !EAX_UTILS_INCLUDED diff --git a/libs/openal-soft/al/eax/x_ram.h b/libs/openal-soft/al/eax/x_ram.h index 39bb778b..3616550d 100644 --- a/libs/openal-soft/al/eax/x_ram.h +++ b/libs/openal-soft/al/eax/x_ram.h @@ -24,12 +24,7 @@ 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"; -/* NOLINTBEGIN(readability-inconsistent-declaration-parameter-name) - * These functions are defined using macros to forward them in a generic way to - * implementation functions, which gives the parameters generic names. - */ ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept; ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept; -/* NOLINTEND(readability-inconsistent-declaration-parameter-name) */ #endif // !EAX_X_RAM_INCLUDED diff --git a/libs/openal-soft/al/effect.cpp b/libs/openal-soft/al/effect.cpp index 54c8e0c3..0c549d8a 100644 --- a/libs/openal-soft/al/effect.cpp +++ b/libs/openal-soft/al/effect.cpp @@ -51,9 +51,9 @@ #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "core/except.h" #include "core/logging.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -109,11 +109,38 @@ constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps& void InitEffectParams(ALeffect *effect, ALenum type) noexcept { + switch(type) + { + case AL_EFFECT_NULL: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_REVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace(); break; + case AL_EFFECT_CHORUS: effect->PropsVariant.emplace(); break; + case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace(); break; + case AL_EFFECT_ECHO: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FLANGER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DEDICATED_DIALOGUE: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_CONVOLUTION_SOFT: + effect->PropsVariant.emplace(); + break; + } effect->Props = GetDefaultProps(type); effect->type = type; } -auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureEffects(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t @@ -136,7 +163,8 @@ catch(...) { return false; } -ALeffect *AllocEffect(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocEffect(al::Device *device) noexcept -> ALeffect* { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool @@ -156,7 +184,7 @@ ALeffect *AllocEffect(ALCdevice *device) noexcept return effect; } -void FreeEffect(ALCdevice *device, ALeffect *effect) +void FreeEffect(al::Device *device, ALeffect *effect) { device->mEffectNames.erase(effect->id); @@ -169,7 +197,8 @@ void FreeEffect(ALCdevice *device, ALeffect *effect) device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } -inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* +[[nodiscard]] inline +auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -188,21 +217,23 @@ AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} effects", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const al::span eids{effects, static_cast(n)}; if(!EnsureEffects(device, eids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effect{}", n, + (n==1) ? "" : "s"); std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) @@ -210,11 +241,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei const ALuint *effects) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} effects", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; /* First try to find any effects that are invalid. */ auto validate_effect = [device](const ALuint eid) -> bool @@ -223,7 +254,7 @@ try { const al::span eids{effects, static_cast(n)}; auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); if(inveffect != eids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", *inveffect); /* All good. Delete non-0 effect IDs. */ auto delete_effect = [device](ALuint eid) -> void @@ -233,15 +264,17 @@ try { }; std::for_each(eids.begin(), eids.end(), delete_effect); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; if(!effect || LookupEffect(device, effect)) return AL_TRUE; return AL_FALSE; @@ -251,12 +284,12 @@ AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { @@ -266,8 +299,8 @@ try { auto check_effect = [value](const EffectList &item) -> bool { return value == item.val && !DisabledEffects.test(item.type); }; if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) - throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", - value}; + context->throw_error(AL_INVALID_VALUE, "Effect type {:#04x} not supported", + as_unsigned(value)); } InitEffectParams(aleffect, value); @@ -275,19 +308,17 @@ try { } /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbSetParami(arg, param, value); - } - return EffectHandler::SetParami(arg, param, value); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.SetParami(context, std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) @@ -301,93 +332,87 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbSetParamiv(arg, param, values); - } - return EffectHandler::SetParamiv(arg, param, values); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.SetParamiv(context, std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + if(!aleffect) + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbSetParamf(arg, param, value); - } - return EffectHandler::SetParamf(arg, param, value); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.SetParamf(context, std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbSetParamfv(arg, param, values); - } - return EffectHandler::SetParamfv(arg, param, values); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.SetParamfv(context, std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { @@ -397,19 +422,17 @@ try { } /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbGetParami(arg, param, value); - } - return EffectHandler::GetParami(arg, param, value); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.GetParami(context, std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) @@ -423,81 +446,75 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbGetParamiv(arg, param, values); - } - return EffectHandler::GetParamiv(arg, param, values); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.GetParamiv(context, std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,value](auto &arg) + std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbGetParamf(arg, param, value); - } - return EffectHandler::GetParamf(arg, param, value); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.GetParamf(context, std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ - std::visit([aleffect,param,values](auto &arg) + std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; - if constexpr(std::is_same_v) - { - if(aleffect->type == AL_EFFECT_REVERB) - return EffectHandler::StdReverbGetParamfv(arg, param, values); - } - return EffectHandler::GetParamfv(arg, param, values); - }, aleffect->Props); + using PropType = typename Type::prop_type; + return arg.GetParamfv(context, std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -508,12 +525,12 @@ void InitEffect(ALeffect *effect) void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard effectlock{device->EffectLock}; + auto *device = context->mALDevice.get(); + auto effectlock = std::lock_guard{device->EffectLock}; auto effect = LookupEffect(device, id); if(!effect) - throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id); device->mEffectNames.insert_or_assign(id, name); } @@ -679,7 +696,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); - TRACE("Loading reverb '%s'\n", "NONE"); + TRACE("Loading reverb '{}'", "NONE"); return; } @@ -694,7 +711,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; - TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); + TRACE("Loading reverb '{}'", std::data(reverbitem.name)); const auto &props = reverbitem.props; auto &dst = std::get(effect->Props); dst.Density = props.flDensity; @@ -727,7 +744,7 @@ void LoadReverbPreset(const std::string_view name, ALeffect *effect) return; } - WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); + WARN("Reverb preset '{}' not found", name); } bool IsValidEffectType(ALenum type) noexcept diff --git a/libs/openal-soft/al/effect.h b/libs/openal-soft/al/effect.h index 6e396721..c7db522a 100644 --- a/libs/openal-soft/al/effect.h +++ b/libs/openal-soft/al/effect.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -14,6 +15,7 @@ #include "almalloc.h" #include "alnumeric.h" #include "core/effects/base.h" +#include "effects/effects.h" enum { @@ -42,14 +44,20 @@ struct EffectList { ALuint type; ALenum val; }; -extern const std::array gEffectList; +DECL_HIDDEN extern const std::array gEffectList; +using EffectHandlerVariant = std::variant; struct ALeffect { // Effect type (AL_EFFECT_NULL, ...) ALenum type{AL_EFFECT_NULL}; - EffectProps Props{}; + EffectHandlerVariant PropsVariant; + EffectProps Props; /* Self ID */ ALuint id{0u}; diff --git a/libs/openal-soft/al/effects/autowah.cpp b/libs/openal-soft/al/effects/autowah.cpp index c8b92450..7519d31b 100644 --- a/libs/openal-soft/al/effects/autowah.cpp +++ b/libs/openal-soft/al/effects/autowah.cpp @@ -4,15 +4,13 @@ #include #include -#include - #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -35,75 +33,68 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps AutowahEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(AutowahProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; -} +void AutowahEffectHandler::SetParami(ALCcontext *context, AutowahProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } +void AutowahEffectHandler::SetParamiv(ALCcontext *context, AutowahProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } -void EffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) +void AutowahEffectHandler::SetParamf(ALCcontext *context, AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah attack time out of range"); props.AttackTime = val; - break; + return; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah release time out of range"); props.ReleaseTime = val; - break; + return; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) - throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah resonance out of range"); props.Resonance = val; - break; + return; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Autowah peak gain out of range"); props.PeakGain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const AutowahProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}", + as_unsigned(param)); } +void AutowahEffectHandler::SetParamfv(ALCcontext *context, AutowahProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } -void EffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) +void AutowahEffectHandler::GetParami(ALCcontext *context, const AutowahProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } +void AutowahEffectHandler::GetParamiv(ALCcontext *context, const AutowahProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } + +void AutowahEffectHandler::GetParamf(ALCcontext *context, const AutowahProps &props, ALenum param, float *val) { switch(param) { - case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; - case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; - case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; - case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", 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 EffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void AutowahEffectHandler::GetParamfv(ALCcontext *context, const AutowahProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using AutowahCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/chorus.cpp b/libs/openal-soft/al/effects/chorus.cpp index 93771a1f..bb9600bf 100644 --- a/libs/openal-soft/al/effects/chorus.cpp +++ b/libs/openal-soft/al/effects/chorus.cpp @@ -7,13 +7,13 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "core/logging.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -44,7 +44,8 @@ constexpr ALenum EnumFromWaveform(ChorusWaveform type) case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID; case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE; } - throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid chorus waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultChorusProps() noexcept @@ -61,7 +62,7 @@ constexpr EffectProps genDefaultChorusProps() noexcept constexpr EffectProps genDefaultFlangerProps() noexcept { - FlangerProps props{}; + ChorusProps props{}; props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(); props.Phase = AL_FLANGER_DEFAULT_PHASE; props.Rate = AL_FLANGER_DEFAULT_RATE; @@ -75,7 +76,7 @@ constexpr EffectProps genDefaultFlangerProps() noexcept const EffectProps ChorusEffectProps{genDefaultChorusProps()}; -void EffectHandler::SetParami(ChorusProps &props, ALenum param, int val) +void ChorusEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { @@ -83,89 +84,90 @@ void EffectHandler::SetParami(ChorusProps &props, ALenum param, int val) if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; - break; + 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)) - throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus phase out of range: {}", val); props.Phase = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) +void ChorusEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void ChorusEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus rate out of range: {:f}", val); props.Rate = val; - break; + return; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus depth out of range: {:f}", val); props.Depth = val; - break; + return; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus feedback out of range: {:f}", val); props.Feedback = val; - break; + return; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Chorus delay out of range: {:f}", val); props.Delay = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", + as_unsigned(param)); +} +void ChorusEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void ChorusEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - case AL_CHORUS_PHASE: *val = props.Phase; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", 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 EffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) +void ChorusEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ChorusEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_CHORUS_RATE: *val = props.Rate; break; - case AL_CHORUS_DEPTH: *val = props.Depth; break; - case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; - case AL_CHORUS_DELAY: *val = props.Delay; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", 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 EffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void ChorusEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; -void EffectHandler::SetParami(FlangerProps &props, ALenum param, int val) +void FlangerEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { @@ -173,93 +175,93 @@ void EffectHandler::SetParami(FlangerProps &props, ALenum param, int val) if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; - break; + 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)) - throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger phase out of range: {}", val); props.Phase = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamiv(FlangerProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(FlangerProps &props, ALenum param, float val) +void FlangerEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void FlangerEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger rate out of range: {:f}", val); props.Rate = val; - break; + return; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger depth out of range: {:f}", val); props.Depth = val; - break; + return; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger feedback out of range: {:f}", val); props.Feedback = val; - break; + return; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Flanger delay out of range: {:f}", val); props.Delay = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(FlangerProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const FlangerProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", + as_unsigned(param)); +} +void FlangerEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void FlangerEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - case AL_FLANGER_PHASE: *val = props.Phase; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", 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 EffectHandler::GetParamiv(const FlangerProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const FlangerProps &props, ALenum param, float *val) +void FlangerEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void FlangerEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_FLANGER_RATE: *val = props.Rate; break; - case AL_FLANGER_DEPTH: *val = props.Depth; break; - case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; - case AL_FLANGER_DELAY: *val = props.Delay; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", 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 EffectHandler::GetParamfv(const FlangerProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void FlangerEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { struct EaxChorusTraits { using EaxProps = EAXCHORUSPROPERTIES; using Committer = EaxChorusCommitter; - using AlProps = ChorusProps; static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } @@ -325,7 +327,6 @@ struct EaxChorusTraits { struct EaxFlangerTraits { using EaxProps = EAXFLANGERPROPERTIES; using Committer = EaxFlangerCommitter; - using AlProps = FlangerProps; static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } @@ -393,7 +394,6 @@ struct ChorusFlangerEffect { using Traits = TTraits; using EaxProps = typename Traits::EaxProps; using Committer = typename Traits::Committer; - using AlProps = typename Traits::AlProps; using Exception = typename Committer::Exception; struct WaveformValidator { @@ -551,7 +551,7 @@ public: } } - static bool Commit(const EaxProps &props, EaxEffectProps &props_, AlProps &al_props_) + static bool Commit(const EaxProps &props, EaxEffectProps &props_, ChorusProps &al_props_) { if(auto *cur = std::get_if(&props_); cur && *cur == props) return false; @@ -564,6 +564,17 @@ public: 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; } @@ -628,7 +639,7 @@ template<> bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; - return Committer::Commit(props, mEaxProps, mAlProps.emplace()); + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props) diff --git a/libs/openal-soft/al/effects/compressor.cpp b/libs/openal-soft/al/effects/compressor.cpp index a388dcef..f5c6786a 100644 --- a/libs/openal-soft/al/effects/compressor.cpp +++ b/libs/openal-soft/al/effects/compressor.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -28,56 +28,46 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps CompressorEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(CompressorProps &props, ALenum param, int val) +void CompressorEffectHandler::SetParami(ALCcontext *context, CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) - throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; + context->throw_error(AL_INVALID_VALUE, "Compressor state out of range"); props.OnOff = (val != AL_FALSE); - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param}; + return; } -} -void EffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(CompressorProps&, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void EffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", - param}; -} -void EffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", + as_unsigned(param)); +} +void CompressorEffectHandler::SetParamiv(ALCcontext *context, CompressorProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void CompressorEffectHandler::SetParamf(ALCcontext *context, CompressorProps&, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } +void CompressorEffectHandler::SetParamfv(ALCcontext *context, CompressorProps&, ALenum param, const float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } + +void CompressorEffectHandler::GetParami(ALCcontext *context, const CompressorProps &props, ALenum param, int *val) { switch(param) { - case AL_COMPRESSOR_ONOFF: - *val = props.OnOff; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", - param}; + case AL_COMPRESSOR_ONOFF: *val = props.OnOff; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void EffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", - param}; -} +void CompressorEffectHandler::GetParamiv(ALCcontext *context, const CompressorProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void CompressorEffectHandler::GetParamf(ALCcontext *context, const CompressorProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } +void CompressorEffectHandler::GetParamfv(ALCcontext *context, const CompressorProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using CompressorCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/convolution.cpp b/libs/openal-soft/al/effects/convolution.cpp index 6f31fce3..84de06c1 100644 --- a/libs/openal-soft/al/effects/convolution.cpp +++ b/libs/openal-soft/al/effects/convolution.cpp @@ -7,10 +7,10 @@ #include "AL/al.h" +#include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "alspan.h" -#include "core/effects/base.h" #include "effects.h" @@ -28,90 +28,49 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps ConvolutionEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", - param}; - } -} -void EffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals) -{ - switch(param) - { - default: - SetParami(props, param, *vals); - } -} -void EffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", - param}; - } -} -void EffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values) +void ConvolutionEffectHandler::SetParami(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, int /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::SetParamiv(ALCcontext *context, ConvolutionProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void ConvolutionEffectHandler::SetParamf(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, float /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::SetParamfv(ALCcontext *context, ConvolutionProps &props, ALenum param, const float *values) { static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; - al::span vals; + switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: - vals = {values, 6_uz}; + auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param}; + context->throw_error(AL_INVALID_VALUE, "Convolution orientation out of range", param); std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); - break; - - default: - SetParamf(props, param, *values); + return; } + + SetParamf(context, props, param, *values); } -void EffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +void ConvolutionEffectHandler::GetParami(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::GetParamiv(ALCcontext *context, const ConvolutionProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } + +void ConvolutionEffectHandler::GetParamf(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) +{ context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } +void ConvolutionEffectHandler::GetParamfv(ALCcontext *context, const ConvolutionProps &props, ALenum param, float *values) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", - param}; - } -} -void EffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals) -{ - switch(param) - { - default: - GetParami(props, param, vals); - } -} -void EffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", - param}; - } -} -void EffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values) -{ - al::span vals; switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: - vals = {values, 6_uz}; + auto vals = al::span{values, 6_uz}; std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); - break; - - default: - GetParamf(props, param, values); + return; } + + GetParamf(context, props, param, values); } diff --git a/libs/openal-soft/al/effects/dedicated.cpp b/libs/openal-soft/al/effects/dedicated.cpp index e6e62f56..5776b42b 100644 --- a/libs/openal-soft/al/effects/dedicated.cpp +++ b/libs/openal-soft/al/effects/dedicated.cpp @@ -6,7 +6,8 @@ #include "AL/al.h" #include "AL/alext.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" @@ -14,14 +15,16 @@ namespace { constexpr EffectProps genDefaultDialogProps() noexcept { - DedicatedDialogProps props{}; + DedicatedProps props{}; + props.Target = DedicatedProps::Dialog; props.Gain = 1.0f; return props; } constexpr EffectProps genDefaultLfeProps() noexcept { - DedicatedLfeProps props{}; + DedicatedProps props{}; + props.Target = DedicatedProps::Lfe; props.Gain = 1.0f; return props; } @@ -30,91 +33,81 @@ constexpr EffectProps genDefaultLfeProps() noexcept const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; -void EffectHandler::SetParami(DedicatedDialogProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(DedicatedDialogProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; -} -void EffectHandler::SetParamf(DedicatedDialogProps &props, ALenum param, float val) +void DedicatedDialogEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) - throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(DedicatedDialogProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const DedicatedDialogProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const DedicatedDialogProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamf(const DedicatedDialogProps &props, ALenum param, float *val) +void DedicatedDialogEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DedicatedDialogEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedDialogEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { - case AL_DEDICATED_GAIN: *val = props.Gain; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + case AL_DEDICATED_GAIN: *val = props.Gain; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamfv(const DedicatedDialogProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DedicatedDialogEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; -void EffectHandler::SetParami(DedicatedLfeProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(DedicatedLfeProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; -} -void EffectHandler::SetParamf(DedicatedLfeProps &props, ALenum param, float val) +void DedicatedLfeEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) - throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(DedicatedLfeProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const DedicatedLfeProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const DedicatedLfeProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamf(const DedicatedLfeProps &props, ALenum param, float *val) +void DedicatedLfeEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DedicatedLfeEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } +void DedicatedLfeEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { - case AL_DEDICATED_GAIN: *val = props.Gain; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + case AL_DEDICATED_GAIN: *val = props.Gain; return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamfv(const DedicatedLfeProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DedicatedLfeEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } diff --git a/libs/openal-soft/al/effects/distortion.cpp b/libs/openal-soft/al/effects/distortion.cpp index eb93d8cd..2109e384 100644 --- a/libs/openal-soft/al/effects/distortion.cpp +++ b/libs/openal-soft/al/effects/distortion.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -32,80 +32,76 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps DistortionEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(DistortionProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", - param}; -} -void EffectHandler::SetParamf(DistortionProps &props, ALenum param, float val) +void DistortionEffectHandler::SetParami(ALCcontext *context, DistortionProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } +void DistortionEffectHandler::SetParamiv(ALCcontext *context, DistortionProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } + +void DistortionEffectHandler::SetParamf(ALCcontext *context, DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) - throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion edge out of range"); props.Edge = val; - break; + return; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion gain out of range"); props.Gain = val; - break; + return; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"); props.LowpassCutoff = val; - break; + return; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) - throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion EQ center out of range"); props.EQCenter = val; - break; + return; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; + context->throw_error(AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"); props.EQBandwidth = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const DistortionProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) +void DistortionEffectHandler::SetParamfv(ALCcontext *context, DistortionProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void DistortionEffectHandler::GetParami(ALCcontext *context, const DistortionProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } +void DistortionEffectHandler::GetParamiv(ALCcontext *context, const DistortionProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } + +void DistortionEffectHandler::GetParamf(ALCcontext *context, const DistortionProps &props, ALenum param, float *val) { switch(param) { - case AL_DISTORTION_EDGE: *val = props.Edge; break; - case AL_DISTORTION_GAIN: *val = props.Gain; break; - case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; - case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; - case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", 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 EffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void DistortionEffectHandler::GetParamfv(ALCcontext *context, const DistortionProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using DistortionCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/echo.cpp b/libs/openal-soft/al/effects/echo.cpp index 4605f5b4..2a5faf0d 100644 --- a/libs/openal-soft/al/effects/echo.cpp +++ b/libs/openal-soft/al/effects/echo.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -35,74 +35,74 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps EchoEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(EchoProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void EffectHandler::SetParamf(EchoProps &props, ALenum param, float val) +void EchoEffectHandler::SetParami(ALCcontext *context, EchoProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::SetParamiv(ALCcontext *context, EchoProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::SetParamf(ALCcontext *context, EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo delay out of range"); props.Delay = val; - break; + return; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) - throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo LR delay out of range"); props.LRDelay = val; - break; + return; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) - throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo damping out of range"); props.Damping = val; - break; + return; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) - throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo feedback out of range"); props.Feedback = val; - break; + return; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) - throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; + context->throw_error(AL_INVALID_VALUE, "Echo spread out of range"); props.Spread = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const EchoProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void EffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) + context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}", + as_unsigned(param)); +} +void EchoEffectHandler::SetParamfv(ALCcontext *context, EchoProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void EchoEffectHandler::GetParami(ALCcontext *context, const EchoProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::GetParamiv(ALCcontext *context, const EchoProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } +void EchoEffectHandler::GetParamf(ALCcontext *context, const EchoProps &props, ALenum param, float *val) { switch(param) { - case AL_ECHO_DELAY: *val = props.Delay; break; - case AL_ECHO_LRDELAY: *val = props.LRDelay; break; - case AL_ECHO_DAMPING: *val = props.Damping; break; - case AL_ECHO_FEEDBACK: *val = props.Feedback; break; - case AL_ECHO_SPREAD: *val = props.Spread; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", 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 EffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void EchoEffectHandler::GetParamfv(ALCcontext *context, const EchoProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using EchoCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/effects.h b/libs/openal-soft/al/effects/effects.h index 8dc9f9bc..76e5c910 100644 --- a/libs/openal-soft/al/effects/effects.h +++ b/libs/openal-soft/al/effects/effects.h @@ -3,71 +3,62 @@ #include +#include "AL/alc.h" #include "AL/al.h" -#include "al/error.h" #include "core/effects/base.h" +#include "opthelpers.h" - -struct EffectHandler { -#define DECL_HANDLER(T) \ - static void SetParami(T &props, ALenum param, int val); \ - static void SetParamiv(T &props, ALenum param, const int *vals); \ - static void SetParamf(T &props, ALenum param, float val); \ - static void SetParamfv(T &props, ALenum param, const float *vals); \ - static void GetParami(const T &props, ALenum param, int *val); \ - static void GetParamiv(const T &props, ALenum param, int *vals); \ - static void GetParamf(const T &props, ALenum param, float *val); \ - static void GetParamfv(const T &props, ALenum param, float *vals); - - DECL_HANDLER(std::monostate) - DECL_HANDLER(ReverbProps) - DECL_HANDLER(ChorusProps) - DECL_HANDLER(AutowahProps) - DECL_HANDLER(CompressorProps) - DECL_HANDLER(ConvolutionProps) - DECL_HANDLER(DedicatedDialogProps) - DECL_HANDLER(DedicatedLfeProps) - DECL_HANDLER(DistortionProps) - DECL_HANDLER(EchoProps) - DECL_HANDLER(EqualizerProps) - DECL_HANDLER(FlangerProps) - DECL_HANDLER(FshifterProps) - DECL_HANDLER(ModulatorProps) - DECL_HANDLER(PshifterProps) - DECL_HANDLER(VmorpherProps) -#undef DECL_HANDLER - - static void StdReverbSetParami(ReverbProps &props, ALenum param, int val); - static void StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals); - static void StdReverbSetParamf(ReverbProps &props, ALenum param, float val); - static void StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals); - static void StdReverbGetParami(const ReverbProps &props, ALenum param, int *val); - static void StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals); - static void StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val); - static void StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals); +#define DECL_HANDLER(N, T) \ +struct N { \ + using prop_type = T; \ + \ + static void SetParami(ALCcontext *context, prop_type &props, ALenum param, int val); \ + static void SetParamiv(ALCcontext *context, prop_type &props, ALenum param, const int *vals); \ + static void SetParamf(ALCcontext *context, prop_type &props, ALenum param, float val); \ + static void SetParamfv(ALCcontext *context, prop_type &props, ALenum param, const float *vals);\ + static void GetParami(ALCcontext *context, const prop_type &props, ALenum param, int *val); \ + static void GetParamiv(ALCcontext *context, const prop_type &props, ALenum param, int *vals); \ + static void GetParamf(ALCcontext *context, const prop_type &props, ALenum param, float *val); \ + static void GetParamfv(ALCcontext *context, const prop_type &props, ALenum param, float *vals);\ }; - -using effect_exception = al::context_error; +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. */ -extern const EffectProps NullEffectProps; -extern const EffectProps ReverbEffectProps; -extern const EffectProps StdReverbEffectProps; -extern const EffectProps AutowahEffectProps; -extern const EffectProps ChorusEffectProps; -extern const EffectProps CompressorEffectProps; -extern const EffectProps DistortionEffectProps; -extern const EffectProps EchoEffectProps; -extern const EffectProps EqualizerEffectProps; -extern const EffectProps FlangerEffectProps; -extern const EffectProps FshifterEffectProps; -extern const EffectProps ModulatorEffectProps; -extern const EffectProps PshifterEffectProps; -extern const EffectProps VmorpherEffectProps; -extern const EffectProps DedicatedDialogEffectProps; -extern const EffectProps DedicatedLfeEffectProps; -extern const EffectProps ConvolutionEffectProps; +DECL_HIDDEN extern const EffectProps NullEffectProps; +DECL_HIDDEN extern const EffectProps ReverbEffectProps; +DECL_HIDDEN extern const EffectProps StdReverbEffectProps; +DECL_HIDDEN extern const EffectProps AutowahEffectProps; +DECL_HIDDEN extern const EffectProps ChorusEffectProps; +DECL_HIDDEN extern const EffectProps CompressorEffectProps; +DECL_HIDDEN extern const EffectProps DistortionEffectProps; +DECL_HIDDEN extern const EffectProps EchoEffectProps; +DECL_HIDDEN extern const EffectProps EqualizerEffectProps; +DECL_HIDDEN extern const EffectProps FlangerEffectProps; +DECL_HIDDEN extern const EffectProps FshifterEffectProps; +DECL_HIDDEN extern const EffectProps ModulatorEffectProps; +DECL_HIDDEN extern const EffectProps PshifterEffectProps; +DECL_HIDDEN extern const EffectProps VmorpherEffectProps; +DECL_HIDDEN extern const EffectProps DedicatedDialogEffectProps; +DECL_HIDDEN extern const EffectProps DedicatedLfeEffectProps; +DECL_HIDDEN extern const EffectProps ConvolutionEffectProps; #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/libs/openal-soft/al/effects/equalizer.cpp b/libs/openal-soft/al/effects/equalizer.cpp index f12ad2da..360fd5d8 100644 --- a/libs/openal-soft/al/effects/equalizer.cpp +++ b/libs/openal-soft/al/effects/equalizer.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -37,115 +37,109 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps EqualizerEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(EqualizerProps&, ALenum param, int) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void EffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", - param}; -} -void EffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val) +void EqualizerEffectHandler::SetParami(ALCcontext *context, EqualizerProps&, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::SetParamiv(ALCcontext *context, EqualizerProps&, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::SetParamf(ALCcontext *context, EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer low-band gain out of range"); props.LowGain = val; - break; + return; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"); props.LowCutoff = val; - break; + return; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"); props.Mid1Gain = val; - break; + return; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band center out of range"); props.Mid1Center = val; - break; + return; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band width out of range"); props.Mid1Width = val; - break; + return; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"); props.Mid2Gain = val; - break; + return; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band center out of range"); props.Mid2Center = val; - break; + return; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band width out of range"); props.Mid2Width = val; - break; + return; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer high-band gain out of range"); props.HighGain = val; - break; + return; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; + context->throw_error(AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"); props.HighCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void EffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) +void EqualizerEffectHandler::SetParamfv(ALCcontext *context, EqualizerProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void EqualizerEffectHandler::GetParami(ALCcontext *context, const EqualizerProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::GetParamiv(ALCcontext *context, const EqualizerProps&, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } +void EqualizerEffectHandler::GetParamf(ALCcontext *context, const EqualizerProps &props, ALenum param, float *val) { switch(param) { - case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; - case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; - case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; - case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; - case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; - case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; - case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; - case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; - case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; - case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", 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 EffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void EqualizerEffectHandler::GetParamfv(ALCcontext *context, const EqualizerProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using EqualizerCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/fshifter.cpp b/libs/openal-soft/al/effects/fshifter.cpp index bae00d18..d1c93921 100644 --- a/libs/openal-soft/al/effects/fshifter.cpp +++ b/libs/openal-soft/al/effects/fshifter.cpp @@ -7,12 +7,13 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" + #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -39,7 +40,7 @@ constexpr ALenum EnumFromDirection(FShifterDirection dir) case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP; case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF; } - throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast(dir))}; + throw std::runtime_error{fmt::format("Invalid direction: {}", int{al::to_underlying(dir)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -55,7 +56,7 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps FshifterEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(FshifterProps &props, ALenum param, int val) +void FshifterEffectHandler::SetParami(ALCcontext *context, FshifterProps &props, ALenum param, int val) { switch(param) { @@ -63,81 +64,75 @@ void EffectHandler::SetParami(FshifterProps &props, ALenum param, int val) if(auto diropt = DirectionFromEmum(val)) props.LeftDirection = *diropt; else - throw effect_exception{AL_INVALID_VALUE, - "Unsupported frequency shifter left direction: 0x%04x", val}; - break; + 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 - throw effect_exception{AL_INVALID_VALUE, - "Unsupported frequency shifter right direction: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; + context->throw_error(AL_INVALID_VALUE, + "Unsupported frequency shifter right direction: {:#04x}", as_unsigned(val)); + return; } -} -void EffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(FshifterProps &props, ALenum param, float val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::SetParamiv(ALCcontext *context, FshifterProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void FshifterEffectHandler::SetParamf(ALCcontext *context, FshifterProps &props, ALenum param, float val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; + context->throw_error(AL_INVALID_VALUE, "Frequency shifter frequency out of range"); props.Frequency = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; + return; } -} -void EffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::SetParamfv(ALCcontext *context, FshifterProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void FshifterEffectHandler::GetParami(ALCcontext *context, const FshifterProps &props, ALenum param, int *val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: *val = EnumFromDirection(props.LeftDirection); - break; + return; case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: *val = EnumFromDirection(props.RightDirection); - break; - - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; + return; } -} -void EffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val) + context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", + as_unsigned(param)); +} +void FshifterEffectHandler::GetParamiv(ALCcontext *context, const FshifterProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } + +void FshifterEffectHandler::GetParamf(ALCcontext *context, const FshifterProps &props, ALenum param, float *val) { switch(param) { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props.Frequency; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - 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 EffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void FshifterEffectHandler::GetParamfv(ALCcontext *context, const FshifterProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using FrequencyShifterCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/modulator.cpp b/libs/openal-soft/al/effects/modulator.cpp index a80b59af..b3ca2213 100644 --- a/libs/openal-soft/al/effects/modulator.cpp +++ b/libs/openal-soft/al/effects/modulator.cpp @@ -7,12 +7,13 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" + #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -39,8 +40,8 @@ constexpr ALenum EnumFromWaveform(ModulatorWaveform type) case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH; case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE; } - throw std::runtime_error{"Invalid modulator waveform: " + - std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid modulator waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -56,84 +57,84 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps ModulatorEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) +void ModulatorEffectHandler::SetParami(ALCcontext *context, ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - SetParamf(props, param, static_cast(val)); - break; + SetParamf(context, props, param, static_cast(val)); + return; case AL_RING_MODULATOR_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; + context->throw_error(AL_INVALID_VALUE, "Invalid modulator waveform: {:#04x}", + as_unsigned(val)); + return; } -} -void EffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) + context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", + as_unsigned(param)); +} +void ModulatorEffectHandler::SetParamiv(ALCcontext *context, ModulatorProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } + +void ModulatorEffectHandler::SetParamf(ALCcontext *context, ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Modulator frequency out of range: {:f}", val); props.Frequency = val; - break; + return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + context->throw_error(AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: {:f}", + val); props.HighPassCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + return; } -} -void EffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val) + context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}", + as_unsigned(param)); +} +void ModulatorEffectHandler::SetParamfv(ALCcontext *context, ModulatorProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void ModulatorEffectHandler::GetParami(ALCcontext *context, const ModulatorProps &props, ALenum param, int *val) { switch(param) { - case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); break; - case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; + case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); return; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); return; + case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) +void ModulatorEffectHandler::GetParamiv(ALCcontext *context, const ModulatorProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ModulatorEffectHandler::GetParamf(ALCcontext *context, const ModulatorProps &props, ALenum param, float *val) { switch(param) { - case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", 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 EffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void ModulatorEffectHandler::GetParamfv(ALCcontext *context, const ModulatorProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using ModulatorCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/null.cpp b/libs/openal-soft/al/effects/null.cpp index 02d24175..ac522313 100644 --- a/libs/openal-soft/al/effects/null.cpp +++ b/libs/openal-soft/al/effects/null.cpp @@ -4,10 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #endif // ALSOFT_EAX @@ -24,78 +25,46 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps NullEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/) +void NullEffectHandler::SetParami(ALCcontext *context, std::monostate& /*props*/, ALenum param, int /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) +void NullEffectHandler::SetParamiv(ALCcontext *context, std::monostate &props, ALenum param, const int *vals) { - switch(param) - { - default: - SetParami(props, param, *vals); - } + SetParami(context, props, param, *vals); } -void EffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/) +void NullEffectHandler::SetParamf(ALCcontext *context, std::monostate& /*props*/, ALenum param, float /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) +void NullEffectHandler::SetParamfv(ALCcontext *context, std::monostate &props, ALenum param, const float *vals) { - switch(param) - { - default: - SetParamf(props, param, *vals); - } + SetParamf(context, props, param, *vals); } -void EffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/) +void NullEffectHandler::GetParami(ALCcontext *context, const std::monostate& /*props*/, ALenum param, int* /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) +void NullEffectHandler::GetParamiv(ALCcontext *context, const std::monostate &props, ALenum param, int *vals) { - switch(param) - { - default: - GetParami(props, param, vals); - } + GetParami(context, props, param, vals); } -void EffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/) +void NullEffectHandler::GetParamf(ALCcontext *context, const std::monostate& /*props*/, ALenum param, float* /*val*/) { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } + context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) +void NullEffectHandler::GetParamfv(ALCcontext *context, const std::monostate &props, ALenum param, float *vals) { - switch(param) - { - default: - GetParamf(props, param, vals); - } + GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using NullCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/pshifter.cpp b/libs/openal-soft/al/effects/pshifter.cpp index 7b91dd1f..c07ef275 100644 --- a/libs/openal-soft/al/effects/pshifter.cpp +++ b/libs/openal-soft/al/effects/pshifter.cpp @@ -4,11 +4,11 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX -#include "alnumeric.h" +#if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -29,63 +29,55 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps PshifterEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(PshifterProps &props, ALenum param, int val) +void PshifterEffectHandler::SetParami(ALCcontext *context, PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) - throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; + context->throw_error(AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"); props.CoarseTune = val; - break; + return; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) - throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; + context->throw_error(AL_INVALID_VALUE, "Pitch shifter fine tune out of range"); props.FineTune = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - param}; + return; } -} -void EffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(PshifterProps&, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void EffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}", + as_unsigned(param)); } +void PshifterEffectHandler::SetParamiv(ALCcontext *context, PshifterProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } -void EffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) +void PshifterEffectHandler::SetParamf(ALCcontext *context, PshifterProps&, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } +void PshifterEffectHandler::SetParamfv(ALCcontext *context, PshifterProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void PshifterEffectHandler::GetParami(ALCcontext *context, const PshifterProps &props, ALenum param, int *val) { switch(param) { - case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; - case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", - 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 EffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } +void PshifterEffectHandler::GetParamiv(ALCcontext *context, const PshifterProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } -void EffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void EffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", - param}; -} +void PshifterEffectHandler::GetParamf(ALCcontext *context, const PshifterProps&, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } +void PshifterEffectHandler::GetParamfv(ALCcontext *context, const PshifterProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using PitchShifterCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/effects/reverb.cpp b/libs/openal-soft/al/effects/reverb.cpp index 5030e9e9..d3b83a08 100644 --- a/libs/openal-soft/al/effects/reverb.cpp +++ b/libs/openal-soft/al/effects/reverb.cpp @@ -9,13 +9,17 @@ #include "AL/al.h" #include "AL/efx.h" +#include "alc/context.h" #include "alnumeric.h" #include "alspan.h" -#include "core/effects/base.h" +#include "core/logging.h" #include "effects.h" +#include "fmt/ranges.h" +#include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include + #include "al/eax/api.h" #include "al/eax/call.h" #include "al/eax/effect.h" @@ -92,152 +96,149 @@ constexpr EffectProps genDefaultStdProps() noexcept const EffectProps ReverbEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(ReverbProps &props, ALenum param, int val) +void ReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) -{ SetParami(props, param, *vals); } -void EffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) +void ReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void ReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; - break; + return; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; - break; + return; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; - break; + return; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; - break; + return; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainlf out of range"); props.GainLF = val; - break; + return; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; - break; + return; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; - break; + return; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"); props.DecayLFRatio = val; - break; + return; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; - break; + return; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; - break; + return; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; - break; + return; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; - break; + return; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo time out of range"); props.EchoTime = val; - break; + return; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo depth out of range"); props.EchoDepth = val; - break; + return; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation time out of range"); props.ModulationTime = val; - break; + return; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"); props.ModulationDepth = val; - break; + return; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; - break; + return; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb hfreference out of range"); props.HFReference = val; - break; + return; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb lfreference out of range"); props.LFReference = val; - break; + return; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + return; } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) +void ReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) { static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; al::span values; @@ -246,64 +247,60 @@ void EffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *va case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"); std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); - break; + return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"); std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); - break; - - default: - SetParamf(props, param, *vals); - break; + return; } + SetParamf(context, props, param, *vals); } -void EffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) +void ReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - 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 EffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) -{ GetParami(props, param, vals); } -void EffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +void ReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void ReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_EAXREVERB_DENSITY: *val = props.Density; break; - case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break; - case AL_EAXREVERB_GAIN: *val = props.Gain; break; - case AL_EAXREVERB_GAINHF: *val = props.GainHF; break; - case AL_EAXREVERB_GAINLF: *val = props.GainLF; break; - case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break; - case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; - case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break; - case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; - case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; - case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; - case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; - case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break; - case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break; - case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break; - case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break; - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; - case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break; - case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break; - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", 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 EffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +void ReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) { al::span values; switch(param) @@ -311,173 +308,155 @@ void EffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *va case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); - break; + return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); - break; - - default: - GetParamf(props, param, vals); - break; + return; } + + GetParamf(context, props, param, vals); } const EffectProps StdReverbEffectProps{genDefaultStdProps()}; -void EffectHandler::StdReverbSetParami(ReverbProps &props, ALenum param, int val) +void StdReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_REVERB_DECAY_HFLIMIT: if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", + as_unsigned(param)); } -void EffectHandler::StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals) -{ StdReverbSetParami(props, param, *vals); } -void EffectHandler::StdReverbSetParamf(ReverbProps &props, ALenum param, float val) +void StdReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void StdReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_REVERB_DENSITY: if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; - break; + return; case AL_REVERB_DIFFUSION: if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; - break; + return; case AL_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; - break; + return; case AL_REVERB_GAINHF: if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; - break; + return; case AL_REVERB_DECAY_TIME: if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; - break; + return; case AL_REVERB_DECAY_HFRATIO: if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; - break; + return; case AL_REVERB_REFLECTIONS_GAIN: if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; - break; + return; case AL_REVERB_REFLECTIONS_DELAY: if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; - break; + return; case AL_REVERB_LATE_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; - break; + return; case AL_REVERB_LATE_REVERB_DELAY: if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; - break; + return; case AL_REVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; - break; + return; case AL_REVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + return; } + + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } -void EffectHandler::StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals) +void StdReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void StdReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { - default: - StdReverbSetParamf(props, param, *vals); - break; + 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 EffectHandler::StdReverbGetParami(const ReverbProps &props, ALenum param, int *val) +void StdReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void StdReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", - 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; } -} -void EffectHandler::StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals) -{ StdReverbGetParami(props, param, vals); } -void EffectHandler::StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val) -{ - switch(param) - { - case AL_REVERB_DENSITY: *val = props.Density; break; - case AL_REVERB_DIFFUSION: *val = props.Diffusion; break; - case AL_REVERB_GAIN: *val = props.Gain; break; - case AL_REVERB_GAINHF: *val = props.GainHF; break; - case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break; - case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; - case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; - case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; - case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; - case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; - case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; - case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; - } -} -void EffectHandler::StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals) -{ - switch(param) - { - default: - StdReverbGetParamf(props, param, vals); - break; - } + context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", + as_unsigned(param)); } +void StdReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { class EaxReverbEffectException : public EaxException @@ -1078,6 +1057,38 @@ bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) ret.LFReference = props.flLFReference; ret.RoomRolloffFactor = props.flRoomRolloffFactor; ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + if(EaxTraceCommits) UNLIKELY + { + 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" + " LateRevernPan: {}\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, ret.LateReverbGain, ret.LateReverbDelay, + ret.LateReverbPan, ret.EchoTime, ret.EchoDepth, ret.ModulationTime, + ret.ModulationDepth, ret.AirAbsorptionGainHF, ret.HFReference, ret.LFReference, + ret.RoomRolloffFactor, ret.DecayHFLimit ? "true" : "false"); + } return ret; }(); diff --git a/libs/openal-soft/al/effects/vmorpher.cpp b/libs/openal-soft/al/effects/vmorpher.cpp index 72749f4e..982bc7e4 100644 --- a/libs/openal-soft/al/effects/vmorpher.cpp +++ b/libs/openal-soft/al/effects/vmorpher.cpp @@ -7,12 +7,12 @@ #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alc/context.h" +#include "alnumeric.h" #include "effects.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include -#include "alnumeric.h" #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" @@ -97,7 +97,7 @@ constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } - throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast(phenome))}; + throw std::runtime_error{fmt::format("Invalid phenome: {}", int{al::to_underlying(phenome)})}; #undef HANDLE_PHENOME } @@ -119,8 +119,8 @@ constexpr ALenum EnumFromWaveform(VMorpherWaveform type) case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE; case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH; } - throw std::runtime_error{"Invalid vocal morpher waveform: " + - std::to_string(static_cast(type))}; + throw std::runtime_error{fmt::format("Invalid vocal morpher waveform: {}", + int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept @@ -139,7 +139,7 @@ constexpr EffectProps genDefaultProps() noexcept const EffectProps VmorpherEffectProps{genDefaultProps()}; -void EffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) +void VmorpherEffectHandler::SetParami(ALCcontext *context, VmorpherProps &props, ALenum param, int val) { switch(param) { @@ -147,101 +147,94 @@ void EffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeA = *phenomeopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; - break; + 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)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-a coarse tuning out of range"); props.PhonemeACoarseTuning = val; - break; + return; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeB = *phenomeopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; - break; + 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)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; + context->throw_error(AL_INVALID_VALUE, + "Vocal morpher phoneme-b coarse tuning out of range"); props.PhonemeBCoarseTuning = val; - break; + return; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - param}; + 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 EffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", - param}; -} -void EffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val) +void VmorpherEffectHandler::SetParamiv(ALCcontext *context, VmorpherProps &props, ALenum param, const int *vals) +{ SetParami(context, props, param, *vals); } +void VmorpherEffectHandler::SetParamf(ALCcontext *context, VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) - throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; + context->throw_error(AL_INVALID_VALUE, "Vocal morpher rate out of range"); props.Rate = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - param}; + return; } -} -void EffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals) -{ SetParamf(props, param, *vals); } -void EffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) + context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}", + as_unsigned(param)); +} +void VmorpherEffectHandler::SetParamfv(ALCcontext *context, VmorpherProps &props, ALenum param, const float *vals) +{ SetParamf(context, props, param, *vals); } + +void VmorpherEffectHandler::GetParami(ALCcontext *context, const VmorpherProps &props, ALenum param, int* val) { switch(param) { - case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; - case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; - case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", - 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 EffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", - param}; -} -void EffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val) +void VmorpherEffectHandler::GetParamiv(ALCcontext *context, const VmorpherProps &props, ALenum param, int *vals) +{ GetParami(context, props, param, vals); } +void VmorpherEffectHandler::GetParamf(ALCcontext *context, const VmorpherProps &props, ALenum param, float *val) { switch(param) { - case AL_VOCAL_MORPHER_RATE: - *val = props.Rate; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", - 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 EffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals) -{ GetParamf(props, param, vals); } +void VmorpherEffectHandler::GetParamfv(ALCcontext *context, const VmorpherProps &props, ALenum param, float *vals) +{ GetParamf(context, props, param, vals); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { using VocalMorpherCommitter = EaxCommitter; diff --git a/libs/openal-soft/al/error.cpp b/libs/openal-soft/al/error.cpp index 24cc51dc..4d2015ce 100644 --- a/libs/openal-soft/al/error.cpp +++ b/libs/openal-soft/al/error.cpp @@ -20,24 +20,19 @@ #include "config.h" -#include "error.h" - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif -#include #include #include #include #include #include -#include #include #include #include -#include #include "AL/al.h" #include "AL/alc.h" @@ -45,53 +40,19 @@ #include "al/debug.h" #include "alc/alconfig.h" #include "alc/context.h" -#include "alc/inprogext.h" +#include "alnumeric.h" +#include "core/except.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" -namespace al { -context_error::context_error(ALenum code, const char *msg, ...) : mErrorCode{code} +void ALCcontext::setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args) { - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ -} -context_error::~context_error() = default; -} /* namespace al */ + const auto msg = fmt::vformat(fmt, std::move(args)); -void ALCcontext::setError(ALenum errorCode, const char *msg, ...) -{ - auto message = std::vector(256); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, msg); - va_copy(args2, args); - int msglen{std::vsnprintf(message.data(), message.size(), msg, args)}; - if(msglen >= 0 && static_cast(msglen) >= message.size()) - { - message.resize(static_cast(msglen) + 1u); - msglen = std::vsnprintf(message.data(), message.size(), msg, args2); - } - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - - if(msglen >= 0) - msg = message.data(); - else - { - msg = ""; - msglen = static_cast(strlen(msg)); - } - - WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", - decltype(std::declval()){this}, errorCode, msg); + WARN("Error generated on context {}, code {:#04x}, \"{}\"", + decltype(std::declval()){this}, as_unsigned(errorCode), msg); if(TrapALError) { #ifdef _WIN32 @@ -106,10 +67,18 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) if(mLastThreadError.get() == AL_NO_ERROR) mLastThreadError.set(errorCode); - debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High, - {msg, static_cast(msglen)}); + debugMessage(DebugSource::API, DebugType::Error, static_cast(errorCode), + DebugSeverity::High, msg); } +void ALCcontext::throw_error_impl(ALenum errorCode, const fmt::string_view fmt, + fmt::format_args args) +{ + setErrorImpl(errorCode, fmt, std::move(args)); + throw al::base_exception{}; +} + + /* Special-case alGetError since it (potentially) raises a debug signal and * returns a non-default value for a null context. */ @@ -125,17 +94,20 @@ AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum optstr = ConfigValueStr({}, "game_compat", optname); if(optstr) { - char *end{}; - auto value = std::strtoul(optstr->c_str(), &end, 0); - if(end && *end == '\0' && value <= std::numeric_limits::max()) - return static_cast(value); - ERR("Invalid default error value: \"%s\"", optstr->c_str()); + try { + auto idx = 0_uz; + auto value = std::stoi(*optstr, &idx, 0); + if(idx >= optstr->size() || std::isspace(optstr->at(idx))) + return static_cast(value); + } catch(...) { + } + ERR("Invalid default error value: \"{}\"", *optstr); } return AL_INVALID_OPERATION; }; static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; - WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); + WARN("Querying error state on null context (implicitly {:#04x})", as_unsigned(deferror)); if(TrapALError) { #ifdef _WIN32 diff --git a/libs/openal-soft/al/error.h b/libs/openal-soft/al/error.h deleted file mode 100644 index c50011a5..00000000 --- a/libs/openal-soft/al/error.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef AL_ERROR_H -#define AL_ERROR_H - -#include "AL/al.h" - -#include "core/except.h" - -namespace al { - -class context_error final : public al::base_exception { - ALenum mErrorCode{}; - -public: -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - context_error(ALenum code, const char *msg, ...); - ~context_error() final; - - [[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; } -}; - -} /* namespace al */ - -#endif /* AL_ERROR_H */ diff --git a/libs/openal-soft/al/event.cpp b/libs/openal-soft/al/event.cpp index 5b8f4458..9a8d50b6 100644 --- a/libs/openal-soft/al/event.cpp +++ b/libs/openal-soft/al/event.cpp @@ -3,7 +3,6 @@ #include "event.h" -#include #include #include #include @@ -12,10 +11,8 @@ #include #include #include -#include #include #include -#include #include #include "AL/al.h" @@ -23,16 +20,17 @@ #include "AL/alext.h" #include "alc/context.h" -#include "alc/inprogext.h" +#include "alnumeric.h" #include "alsem.h" #include "alspan.h" +#include "alstring.h" #include "core/async_event.h" #include "core/context.h" #include "core/effects/base.h" +#include "core/except.h" #include "core/logging.h" #include "debug.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -52,14 +50,15 @@ int EventThread(ALCcontext *context) bool quitnow{false}; while(!quitnow) { - auto evt_data = ring->getReadVector().first; + auto evt_data = ring->getReadVector()[0]; if(evt_data.len == 0) { context->mEventSem.wait(); continue; } - std::lock_guard eventlock{context->mEventCbLock}; + auto eventlock = std::lock_guard{context->mEventCbLock}; + const auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); auto evt_span = al::span{std::launder(reinterpret_cast(evt_data.buf)), evt_data.len}; for(auto &event : evt_span) @@ -67,7 +66,6 @@ int EventThread(ALCcontext *context) quitnow = std::holds_alternative(event); if(quitnow) UNLIKELY break; - auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); auto proc_killthread = [](AsyncKillThread&) { }; auto proc_release = [](AsyncEffectReleaseEvent &evt) { @@ -102,7 +100,7 @@ int EventThread(ALCcontext *context) break; } context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, - static_cast(msg.length()), msg.c_str(), context->mEventParam); + al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) { @@ -114,20 +112,16 @@ int EventThread(ALCcontext *context) if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, - static_cast(msg.length()), msg.c_str(), context->mEventParam); + al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) { - const std::string_view message{evt.msg.data()}; + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) + return; - context->debugMessage(DebugSource::System, DebugType::Error, 0, - DebugSeverity::High, message); - - if(context->mEventCb - && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) - context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, - static_cast(message.length()), message.data(), - context->mEventParam); + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, al::sizei(evt.msg), + evt.msg.c_str(), context->mEventParam); }; std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, @@ -159,22 +153,22 @@ void StartEventThrd(ALCcontext *ctx) ctx->mEventThread = std::thread{EventThread, ctx}; } catch(std::exception& e) { - ERR("Failed to start event thread: %s\n", e.what()); + ERR("Failed to start event thread: {}", e.what()); } catch(...) { - ERR("Failed to start event thread! Expect problems.\n"); + ERR("Failed to start event thread! Expect problems."); } } void StopEventThrd(ALCcontext *ctx) { RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; + auto evt_data = ring->getWriteVector()[0]; if(evt_data.len == 0) { do { std::this_thread::yield(); - evt_data = ring->getWriteVector().first; + evt_data = ring->getWriteVector()[0]; } while(evt_data.len == 0); } std::ignore = InitAsyncEvent(evt_data.buf); @@ -190,18 +184,19 @@ FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsiz const ALenum *types, ALboolean enable) noexcept try { if(count < 0) - throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; + context->throw_error(AL_INVALID_VALUE, "Controlling {} events", count); if(count <= 0) UNLIKELY return; if(!types) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ContextBase::AsyncEventBitset flags{}; for(ALenum evttype : al::span{types, static_cast(count)}) { auto etype = GetEventType(evttype); if(!etype) - throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; + context->throw_error(AL_INVALID_ENUM, "Invalid event type {:#04x}", + as_unsigned(evttype)); flags.set(al::to_underlying(*etype)); } @@ -229,15 +224,22 @@ try { std::lock_guard eventlock{context->mEventCbLock}; } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) noexcept -{ +try { std::lock_guard eventlock{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); +} diff --git a/libs/openal-soft/al/extension.cpp b/libs/openal-soft/al/extension.cpp index 16dc015a..061addc8 100644 --- a/libs/openal-soft/al/extension.cpp +++ b/libs/openal-soft/al/extension.cpp @@ -21,13 +21,11 @@ #include "config.h" #include -#include #include "AL/al.h" #include "AL/alc.h" #include "alc/context.h" -#include "alc/inprogext.h" #include "alstring.h" #include "direct_defs.h" #include "opthelpers.h" diff --git a/libs/openal-soft/al/filter.cpp b/libs/openal-soft/al/filter.cpp index 95b9e10c..938ac48c 100644 --- a/libs/openal-soft/al/filter.cpp +++ b/libs/openal-soft/al/filter.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -41,12 +40,12 @@ #include "albit.h" #include "alc/context.h" #include "alc/device.h" -#include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "core/except.h" +#include "core/logging.h" #include "direct_defs.h" -#include "error.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -97,7 +96,8 @@ void InitFilterParams(ALfilter *filter, ALenum type) filter->type = type; } -auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool +[[nodiscard]] +auto EnsureFilters(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t @@ -121,7 +121,8 @@ catch(...) { } -ALfilter *AllocFilter(ALCdevice *device) noexcept +[[nodiscard]] +auto AllocFilter(al::Device *device) noexcept -> ALfilter* { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool @@ -141,7 +142,7 @@ ALfilter *AllocFilter(ALCdevice *device) noexcept return filter; } -void FreeFilter(ALCdevice *device, ALfilter *filter) +void FreeFilter(al::Device *device, ALfilter *filter) { device->mFilterNames.erase(filter->id); @@ -155,7 +156,8 @@ void FreeFilter(ALCdevice *device, ALfilter *filter) } -inline auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* +[[nodiscard]] +auto LookupFilter(al::Device *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -172,171 +174,176 @@ inline auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* /* Null filter parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter*, ALenum param, const int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamiv(ALCcontext *context, ALfilter*, ALenum param, const int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamf(ALfilter*, ALenum param, float) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamf(ALCcontext *context, ALfilter*, ALenum param, float) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter*, ALenum param, const float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::setParamfv(ALCcontext *context, ALfilter*, ALenum param, const float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamf(const ALfilter*, ALenum param, float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamf(ALCcontext *context, const ALfilter*, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter*, ALenum param, float*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter*, ALenum param, float*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } /* Lowpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_LOWPASS_GAIN: if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Low-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_LOWPASS_GAINHF: if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - throw al::context_error{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Low-pass gainhf {:f} out of range", val); filter->GainHF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_LOWPASS_GAIN: *val = filter->Gain; return; case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } /* Highpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_HIGHPASS_GAIN: if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "High-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_HIGHPASS_GAINLF: if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - throw al::context_error{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "High-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_HIGHPASS_GAIN: *val = filter->Gain; return; case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } /* Bandpass parameter handlers */ template<> -void FilterTable::setParami(ALfilter*, ALenum param, int) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) +{ context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) -{ setParami(filter, param, *values); } +void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) +{ setParami(context, filter, param, *values); } template<> -void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_BANDPASS_GAIN: if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_BANDPASS_GAINHF: if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gainhf {:f} out of range", val); filter->GainHF = val; return; case AL_BANDPASS_GAINLF: if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - throw al::context_error{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; + context->throw_error(AL_INVALID_VALUE, "Band-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ setParamf(filter, param, *vals); } +void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) +{ setParamf(context, filter, param, *vals); } template<> -void FilterTable::getParami(const ALfilter*, ALenum param, int*) -{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) +{ context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> -void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) -{ getParami(filter, param, values); } +void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) +{ getParami(context, filter, param, values); } template<> -void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { @@ -344,32 +351,35 @@ void FilterTable::getParamf(const ALfilter *filter, ALenum case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", + as_unsigned(param)); } template<> -void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ getParamf(filter, param, vals); } +void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) +{ getParamf(context, filter, param, vals); } AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d filters", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} filters", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const al::span fids{filters, static_cast(n)}; if(!EnsureFilters(device, fids.size())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} filter{}", n, + (n==1) ? "" : "s"); std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) @@ -377,11 +387,11 @@ FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei const ALuint *filters) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} filters", n); if(n <= 0) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; /* First try to find any filters that are invalid. */ auto validate_filter = [device](const ALuint fid) -> bool @@ -390,7 +400,7 @@ try { const al::span fids{filters, static_cast(n)}; auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); if(invflt != fids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", *invflt}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", *invflt); /* All good. Delete non-0 filter IDs. */ auto delete_filter = [device](const ALuint fid) -> void @@ -400,15 +410,17 @@ try { }; std::for_each(fids.begin(), fids.end(), delete_filter); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; if(!filter || LookupFilter(device, filter)) return AL_TRUE; return AL_FALSE; @@ -419,29 +431,32 @@ AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { case AL_FILTER_TYPE: if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter type 0x%04x", value}; + context->throw_error(AL_INVALID_VALUE, "Invalid filter type {:#04x}", + as_unsigned(value)); InitFilterParams(alfilt, value); return; } /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.setParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) @@ -455,83 +470,89 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.setParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.setParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.setParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { - case AL_FILTER_TYPE: - *value = alfilt->type; - return; + case AL_FILTER_TYPE: *value = alfilt->type; return; } /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.getParami(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.getParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) @@ -545,68 +566,74 @@ try { return; } - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.getParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + if(!alfilt) + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,value](auto&& thunk) + { thunk.getParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) noexcept try { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + if(!alfilt) + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ - std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);}, - alfilt->mTypeVariant); + std::visit([context,alfilt,param,values](auto&& thunk) + { thunk.getParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard filterlock{device->FilterLock}; + auto *device = context->mALDevice.get(); + auto filterlock = std::lock_guard{device->FilterLock}; auto filter = LookupFilter(device, id); if(!filter) - throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id); device->mFilterNames.insert_or_assign(id, name); } diff --git a/libs/openal-soft/al/filter.h b/libs/openal-soft/al/filter.h index 562ec52e..70b78ca8 100644 --- a/libs/openal-soft/al/filter.h +++ b/libs/openal-soft/al/filter.h @@ -14,21 +14,27 @@ #include "almalloc.h" #include "alnumeric.h" +struct ALfilter; + inline constexpr float LowPassFreqRef{5000.0f}; inline constexpr float HighPassFreqRef{250.0f}; template struct FilterTable { - static void setParami(struct ALfilter*, ALenum, int); - static void setParamiv(struct ALfilter*, ALenum, const int*); - static void setParamf(struct ALfilter*, ALenum, float); - static void setParamfv(struct ALfilter*, ALenum, const float*); + static void setParami(ALCcontext*, ALfilter*, ALenum, int); + static void setParamiv(ALCcontext*, ALfilter*, ALenum, const int*); + static void setParamf(ALCcontext*, ALfilter*, ALenum, float); + static void setParamfv(ALCcontext*, ALfilter*, ALenum, const float*); - static void getParami(const struct ALfilter*, ALenum, int*); - static void getParamiv(const struct ALfilter*, ALenum, int*); - static void getParamf(const struct ALfilter*, ALenum, float*); - static void getParamfv(const struct ALfilter*, ALenum, float*); + static void getParami(ALCcontext*, const ALfilter*, ALenum, int*); + static void getParamiv(ALCcontext*, const ALfilter*, ALenum, int*); + static void getParamf(ALCcontext*, const ALfilter*, ALenum, float*); + static void getParamfv(ALCcontext*, const ALfilter*, ALenum, float*); + +private: + FilterTable() = default; + friend T; }; struct NullFilterTable : public FilterTable { }; diff --git a/libs/openal-soft/al/listener.cpp b/libs/openal-soft/al/listener.cpp index 30ceed04..9addee0c 100644 --- a/libs/openal-soft/al/listener.cpp +++ b/libs/openal-soft/al/listener.cpp @@ -31,11 +31,11 @@ #include "AL/efx.h" #include "alc/context.h" -#include "alc/inprogext.h" +#include "alnumeric.h" #include "alspan.h" +#include "core/except.h" +#include "core/logging.h" #include "direct_defs.h" -#include "error.h" -#include "opthelpers.h" namespace { @@ -54,7 +54,7 @@ inline void CommitAndUpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) { -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->eaxNeedsCommit()) { context->mPropsDirty = true; @@ -79,22 +79,26 @@ try { { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener gain {:f} out of range", value); listener.Gain = value; UpdateProps(context); return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener meters per unit {:f} out of range", + value); listener.mMetersPerUnit = value; UpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -107,7 +111,7 @@ try { { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener position out of range"); listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; @@ -116,17 +120,20 @@ try { case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; + context->throw_error(AL_INVALID_VALUE, "Listener velocity out of range"); listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; CommitAndUpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) @@ -134,7 +141,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum para const ALfloat *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -157,17 +164,20 @@ try { case AL_ORIENTATION: auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) - return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); + context->throw_error(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); CommitAndUpdateProps(context); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -175,10 +185,13 @@ AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept try { std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) @@ -195,10 +208,13 @@ try { } std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) @@ -206,7 +222,7 @@ FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum para const ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); al::span vals; switch(param) @@ -229,11 +245,13 @@ try { } std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -242,7 +260,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum pa ALfloat *value) noexcept try { if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -251,10 +269,13 @@ try { case AL_GAIN: *value = listener.Gain; return; case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) @@ -262,7 +283,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum p ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -280,10 +301,13 @@ try { *value3 = listener.Velocity[2]; return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) @@ -291,7 +315,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum p ALfloat *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -318,22 +342,28 @@ try { std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept try { - if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::lock_guard proplock{context->mPropLock}; - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) @@ -341,7 +371,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum p ALint *value1, ALint *value2, ALint *value3) noexcept try { if(!value1 || !value2 || !value3) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; @@ -359,10 +389,13 @@ try { *value3 = static_cast(listener.Velocity[2]); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) @@ -370,7 +403,7 @@ FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum p ALint *values) noexcept try { if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { @@ -394,9 +427,11 @@ try { std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); return; } - throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", - param}; + context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", + as_unsigned(param)); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } diff --git a/libs/openal-soft/al/source.cpp b/libs/openal-soft/al/source.cpp index d8c9b47f..77f8bfac 100644 --- a/libs/openal-soft/al/source.cpp +++ b/libs/openal-soft/al/source.cpp @@ -28,20 +28,19 @@ #include #include #include -#include #include #include #include -#include #include +#include #include #include #include #include #include -#include #include #include +#include #include #include #include @@ -65,17 +64,17 @@ #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 "error.h" #include "filter.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/fx_slot_index.h" @@ -87,6 +86,12 @@ namespace { using SubListAllocator = al::allocator>; using std::chrono::nanoseconds; using seconds_d = std::chrono::duration; +using source_store_array = std::array; +using source_store_vector = std::vector; +using source_store_variant = std::variant; + +using namespace std::string_view_literals; + Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { @@ -129,7 +134,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->RefDistance = source->RefDistance; props->MaxDistance = source->MaxDistance; props->RolloffFactor = source->RolloffFactor -#ifdef ALSOFT_EAX +#if ALSOFT_EAX + source->RolloffFactor2 #endif ; @@ -199,7 +204,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context */ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; @@ -239,7 +244,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds */ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; @@ -289,7 +294,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl template NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint readPosFrac{}; @@ -327,51 +332,45 @@ NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) } ASSUME(BufferFmt != nullptr); - T offset{}; switch(name) { case AL_SEC_OFFSET: if constexpr(std::is_floating_point_v) { - offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; - offset /= static_cast(BufferFmt->mSampleRate); + auto offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + return offset / static_cast(BufferFmt->mSampleRate); } else { readPos /= BufferFmt->mSampleRate; - offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + return static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); } - break; case AL_SAMPLE_OFFSET: if constexpr(std::is_floating_point_v) - offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + return static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; else - offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + return static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); - break; case AL_BYTE_OFFSET: - const ALuint BlockSamples{BufferFmt->mBlockAlign}; - const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ - readPos = readPos / BlockSamples * BlockSize; + const auto BlockSize = ALuint{BufferFmt->blockSizeFromFmt()}; + readPos = readPos / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) - offset = static_cast(readPos); + return static_cast(readPos); else { if(readPos > std::numeric_limits::max()) - offset = RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); - else if(readPos < std::numeric_limits::min()) - offset = RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); - else - offset = static_cast(readPos); + return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + if(readPos < std::numeric_limits::min()) + return RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); + return static_cast(readPos); } - break; } - return offset; + return T{0}; } /* GetSourceLength @@ -390,10 +389,9 @@ NOINLINE T GetSourceLength(const ALsource *source, ALenum name) BufferFmt = listitem.mBuffer; length += listitem.mSampleLen; } - if(length == 0) + if(length == 0 || !BufferFmt) return T{0}; - ASSUME(BufferFmt != nullptr); switch(name) { case AL_SEC_LENGTH_SOFT: @@ -410,16 +408,15 @@ NOINLINE T GetSourceLength(const ALsource *source, ALenum name) return static_cast(std::min(length, std::numeric_limits::max())); case AL_BYTE_LENGTH_SOFT: - const ALuint BlockSamples{BufferFmt->mBlockAlign}; - const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ - length = length / BlockSamples * BlockSize; + const auto BlockSize = ALuint{BufferFmt->blockSizeFromFmt()}; + length = length / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) return static_cast(length); else { - if(length > std::numeric_limits::max()) + if(length > uint64_t{std::numeric_limits::max()}) return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); return static_cast(length); } @@ -455,44 +452,43 @@ std::optional GetSampleOffset(std::deque &BufferLis return std::nullopt; /* Get sample frame offset */ - int64_t offset{}; - uint frac{}; - double dbloff, dblfrac; - switch(OffsetType) + auto [offset, frac] = std::invoke([OffsetType,Offset,BufferFmt]() -> std::pair { - case AL_SEC_OFFSET: - dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); - if(dblfrac < 0.0) + auto dbloff = double{}; + auto dblfrac = double{}; + switch(OffsetType) { - /* If there's a negative fraction, reduce the offset to "floor" it, - * and convert the fraction to a percentage to the next value (e.g. - * -2.75 -> -3 + 0.25). - */ - dbloff -= 1.0; - dblfrac += 1.0; - } - offset = static_cast(dbloff); - frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); - break; + case AL_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 {static_cast(dbloff), + static_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; - } - offset = static_cast(dbloff); - frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); - break; + case AL_SAMPLE_OFFSET: + dblfrac = std::modf(Offset, &dbloff); + if(dblfrac < 0.0) + { + dbloff -= 1.0; + dblfrac += 1.0; + } + return {static_cast(dbloff), + static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0))}; - case AL_BYTE_OFFSET: - /* Determine the ByteOffset (and ensure it is block aligned) */ - Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); - offset = static_cast(Offset) * BufferFmt->mBlockAlign; - frac = 0; - break; - } + case AL_BYTE_OFFSET: + /* Determine the ByteOffset (and ensure it is block aligned) */ + const auto blockoffset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); + return {static_cast(blockoffset) * BufferFmt->mBlockAlign, 0u}; + } + return {0_i64, 0u}; + }); /* Find the bufferlist item this offset belongs to. */ if(offset < 0) @@ -505,17 +501,14 @@ std::optional GetSampleOffset(std::deque &BufferLis if(BufferFmt->mCallback) return std::nullopt; - int64_t totalBufferLen{0}; for(auto &item : BufferList) { - if(totalBufferLen > offset) - break; - if(item.mSampleLen > offset-totalBufferLen) + if(item.mSampleLen > offset) { /* Offset is in this buffer */ - return VoicePos{static_cast(offset-totalBufferLen), frac, &item}; + return VoicePos{static_cast(offset), frac, &item}; } - totalBufferLen += item.mSampleLen; + offset -= item.mSampleLen; } /* Offset is out of range of the queue */ @@ -524,7 +517,7 @@ std::optional GetSampleOffset(std::deque &BufferLis void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context, - ALCdevice *device) + al::Device *device) { voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr, std::memory_order_relaxed); @@ -575,7 +568,7 @@ VoiceChange *GetVoiceChanger(ALCcontext *ctx) void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) { - ALCdevice *device{ctx->mALDevice.get()}; + auto *device = ctx->mALDevice.get(); VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)}) @@ -604,8 +597,8 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) } -bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, - ALCdevice *device) +auto SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, + al::Device *device) -> bool { /* First, get a free voice to start at the new offset. */ auto voicelist = context->getVoicesSpan(); @@ -758,7 +751,7 @@ ALsource *AllocSource(ALCcontext *context) noexcept ASSUME(slidx < 64); ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX source->eaxInitialize(context); #endif // ALSOFT_EAX @@ -811,7 +804,7 @@ inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept return al::to_address(sublist.Sources->begin() + slidx); } -auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* +auto LookupBuffer = [](al::Device *device, auto id) noexcept -> ALbuffer* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; @@ -824,7 +817,7 @@ auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* return al::to_address(sublist.Buffers->begin() + static_cast(slidx)); }; -auto LookupFilter = [](ALCdevice *device, auto id) noexcept -> ALfilter* +auto LookupFilter = [](al::Device *device, auto id) noexcept -> ALfilter* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; @@ -888,7 +881,8 @@ ALenum EnumFromSpatializeMode(SpatializeMode mode) case SpatializeMode::On: return AL_TRUE; case SpatializeMode::Auto: return AL_AUTO_SOFT; } - throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))}; + throw std::runtime_error{fmt::format("Invalid SpatializeMode: {}", + int{al::to_underlying(mode)})}; } auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional @@ -909,7 +903,7 @@ ALenum EnumFromDirectMode(DirectMode mode) case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT; case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT; } - throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))}; + throw std::runtime_error{fmt::format("Invalid DirectMode: {}", int{al::to_underlying(mode)})}; } auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional @@ -938,7 +932,8 @@ ALenum ALenumFromDistanceModel(DistanceModel model) case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE; case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED; } - throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast(model))}; + throw std::runtime_error{fmt::format("Unexpected distance model: {}", + int{al::to_underlying(model)})}; } enum SourceProp : ALenum { @@ -1348,7 +1343,7 @@ void UpdateSourceProps(ALsource *source, ALCcontext *context) } source->mPropsDirty = true; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) @@ -1372,69 +1367,56 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) template -struct PropType { }; +auto PropTypeName() -> std::string_view = delete; template<> -struct PropType { static const char *Name() { return "integer"; } }; +auto PropTypeName() -> std::string_view { return "integer"sv; } template<> -struct PropType { static const char *Name() { return "int64"; } }; +auto PropTypeName() -> std::string_view { return "int64"sv; } template<> -struct PropType { static const char *Name() { return "float"; } }; +auto PropTypeName() -> std::string_view { return "float"sv; } template<> -struct PropType { static const char *Name() { return "double"; } }; - -struct HexPrinter { - std::array mStr{}; - - template - HexPrinter(T value) - { - using ST = std::make_signed_t>; - if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%x", value); - else if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%lx", value); - else if constexpr(std::is_same_v) - std::snprintf(mStr.data(), mStr.size(), "0x%llx", value); - } - - [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); } -}; +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, - * setting the proper context error and throwing a check_size_exception if it - * fails. + * throwing a context error if it fails. * - * The second lambda tests the validity of the value check, setting the proper - * context error and throwing a check_value_exception if it failed. + * The second lambda tests the validity of the value check, throwing a context + * error if it failed. */ +template +struct PairStruct { T First; U Second; }; +template +PairStruct(T,U) -> PairStruct; + template -auto GetCheckers(const SourceProp prop, const al::span values) +auto GetCheckers(ALCcontext *context, const SourceProp prop, const al::span values) { - return std::make_pair( + return PairStruct{ [=](size_t expect) -> void { if(values.size() == expect) return; - throw al::context_error{AL_INVALID_ENUM, - "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()}; + context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", + as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", + values.size()); }, - [](bool passed) -> void + [context](bool passed) -> void { if(passed) return; - throw al::context_error{AL_INVALID_VALUE, "Value out of range"}; + context->throw_error(AL_INVALID_VALUE, "Value out of range"); } - ); + }; } template NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) { - auto [CheckSize, CheckValue] = GetCheckers(prop, values); - ALCdevice *device{Context->mALDevice.get()}; + auto [CheckSize, CheckValue] = GetCheckers(Context, prop, values); + auto *device = Context->mALDevice.get(); switch(prop) { @@ -1445,8 +1427,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); } break; @@ -1458,12 +1440,15 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x", - prop}; + Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); case AL_PITCH: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->Pitch = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1484,42 +1469,60 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con case AL_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: CheckSize(1); - CheckValue(values[0] >= T{0}); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); Source->MaxGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1601,8 +1604,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Setting buffer on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting buffer on playing or paused source {}", Source->id); std::deque oldlist; if(values[0]) @@ -1611,14 +1614,13 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard buflock{device->BufferLock}; ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; if(!buffer) - throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s", - std::to_string(values[0]).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", values[0]); if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Setting non-persistently mapped buffer %u", buffer->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting non-persistently mapped buffer {}", buffer->id); if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0) - throw al::context_error{AL_INVALID_OPERATION, - "Setting already-set callback buffer %u", buffer->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting already-set callback buffer {}", buffer->id); /* Add the selected buffer to a one-item queue */ std::deque newlist; @@ -1666,7 +1668,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if(Voice *voice{GetSourceVoice(Source, Context)}) { auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast(values[0])); - if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"}; + if(!vpos) Context->throw_error(AL_INVALID_VALUE, "Invalid offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; @@ -1681,8 +1683,9 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); } } break; @@ -1693,8 +1696,9 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { /* Query only */ - throw al::context_error{AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop}; + Context->throw_error(AL_INVALID_OPERATION, + "Setting read-only source property {:#04x}", + as_unsigned(al::to_underlying(prop))); } break; } @@ -1718,8 +1722,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying panning enabled on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Modifying panning enabled on playing or paused source {}", Source->id); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); @@ -1809,8 +1813,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", - std::to_string(filterid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); Source->Direct.Gain = filter->Gain; Source->Direct.GainHF = filter->GainHF; Source->Direct.HFReference = filter->HFReference; @@ -1871,8 +1874,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con Source->DirectChannels = *mode; return UpdateSourceProps(Source, Context); } - throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid direct channels mode: {:#x}", + as_unsigned(values[0])); } break; @@ -1887,8 +1890,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con UpdateSourceProps(Source, Context); return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid distance model: {:#x}", + as_unsigned(values[0])); } break; @@ -1896,7 +1899,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { CheckSize(1); - CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); + CheckValue(values[0] >= 0 && values[0] <= al::to_underlying(Resampler::Max)); Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); @@ -1912,8 +1915,8 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con Source->mSpatialize = *mode; return UpdateSourceProps(Source, Context); } - throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid source spatialize mode: {}", + values[0]); } break; @@ -1923,16 +1926,16 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) - throw al::context_error{AL_INVALID_OPERATION, - "Modifying stereo mode on playing or paused source %u", Source->id}; + Context->throw_error(AL_INVALID_OPERATION, + "Modifying stereo mode on playing or paused source {}", Source->id); if(auto mode = StereoModeFromEnum(values[0])) { Source->mStereoMode = *mode; return; } - throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n", - HexPrinter{values[0]}.c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid stereo mode: {:#x}", + as_unsigned(values[0])); } break; @@ -1950,13 +1953,11 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con { slot = LookupEffectSlot(Context, slotid); if(!slot) - throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s", - std::to_string(slotid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", slotid); } if(sendidx >= device->NumAuxSends) - throw al::context_error{AL_INVALID_VALUE, "Invalid send %s", - std::to_string(sendidx).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid send {}", sendidx); auto &send = Source->Send[static_cast(sendidx)]; if(values[2]) @@ -1964,8 +1965,7 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) - throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", - std::to_string(filterid).c_str()}; + Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); send.Gain = filter->Gain; send.GainHF = filter->GainHF; @@ -2012,20 +2012,19 @@ NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, con break; } - ERR("Unexpected %s property: 0x%04x\n", PropType::Name(), prop); - throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x", - PropType::Name(), prop}; + Context->throw_error(AL_INVALID_ENUM, "Invalid source {} property {:#04x}", PropTypeName(), + as_unsigned(al::to_underlying(prop))); } template -auto GetSizeChecker(const SourceProp prop, const al::span values) +auto GetSizeChecker(ALCcontext *context, const SourceProp prop, const al::span values) { return [=](size_t expect) -> void { if(values.size() == expect) LIKELY return; - throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()}; + context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", + as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", values.size()); }; } @@ -2034,8 +2033,8 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con const al::span values) { using std::chrono::duration_cast; - auto CheckSize = GetSizeChecker(prop, values); - ALCdevice *device{Context->mALDevice.get()}; + auto CheckSize = GetSizeChecker(Context, prop, values); + auto *device = Context->mALDevice.get(); switch(prop) { @@ -2324,7 +2323,7 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con if constexpr(std::is_integral_v) { CheckSize(1); - ALbufferQueueItem *BufferList{}; + const ALbufferQueueItem *BufferList{}; /* HACK: This query should technically only return the buffer set * on a static source. However, some apps had used it to detect * when a streaming source changed buffers, so report the current @@ -2338,7 +2337,10 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con else if(Voice *voice{GetSourceVoice(Source, Context)}) { VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; - BufferList = static_cast(Current); + const auto iter = std::find_if(Source->mQueue.cbegin(), Source->mQueue.cend(), + [Current](const ALbufferQueueItem &item) noexcept -> bool + { return &item == Current; }); + BufferList = (iter != Source->mQueue.cend()) ? al::to_address(iter) : nullptr; } ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; values[0] = buffer ? static_cast(buffer->id) : T{0}; @@ -2482,16 +2484,15 @@ NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, con break; } - ERR("Unexpected %s query property: 0x%04x\n", PropType::Name(), prop); - throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x", - PropType::Name(), prop}; + Context->throw_error(AL_INVALID_ENUM, "Invalid source {} query property {:#04x}", + PropTypeName(), as_unsigned(al::to_underlying(prop))); } void StartSources(ALCcontext *const context, const al::span srchandles, const nanoseconds start_time=nanoseconds::min()) { - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); /* If the device is disconnected, and voices stop on disconnect, go right * to stopped. */ @@ -2580,7 +2581,7 @@ void StartSources(ALCcontext *const context, const al::span srchandle cur->mSourceID = source->id; cur->mState = VChangeState::Play; source->state = AL_PLAYING; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX @@ -2600,7 +2601,7 @@ void StartSources(ALCcontext *const context, const al::span srchandle default: assert(voice == nullptr); cur->mOldVoice = nullptr; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX @@ -2663,24 +2664,27 @@ AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Generating {} sources", n); if(n <= 0) UNLIKELY return; - std::unique_lock srclock{context->mSourceLock}; - ALCdevice *device{context->mALDevice.get()}; + auto srclock = std::unique_lock{context->mSourceLock}; + auto *device = context->mALDevice.get(); const al::span sids{sources, static_cast(n)}; - if(sids.size() > device->SourcesMax-context->mNumSources) - throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", - device->SourcesMax, context->mNumSources, 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())) - throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, - (n == 1) ? "" : "s"}; + context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} source{}", n, + (n==1) ? "" : "s"); std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) @@ -2688,7 +2692,7 @@ FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Deleting {} sources", n); if(n <= 0) UNLIKELY return; std::lock_guard srclock{context->mSourceLock}; @@ -2700,7 +2704,7 @@ try { const al::span sids{sources, static_cast(n)}; auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source); if(invsrc != sids.end()) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", *invsrc); /* All good. Delete source IDs. */ auto delete_source = [&context](const ALuint sid) -> void @@ -2710,8 +2714,10 @@ try { }; std::for_each(sids.begin(), sids.end(), delete_source); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) @@ -2732,12 +2738,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) @@ -2748,13 +2756,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array fvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), fvals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) @@ -2765,15 +2775,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2785,12 +2797,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) @@ -2801,13 +2815,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array dvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), dvals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) @@ -2818,15 +2834,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2838,12 +2856,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) @@ -2854,13 +2874,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array ivals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), ivals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) @@ -2871,15 +2893,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2891,12 +2915,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) @@ -2907,13 +2933,15 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array i64vals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), i64vals); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) @@ -2924,15 +2952,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -2943,14 +2973,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) @@ -2960,9 +2992,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array fvals{}; GetProperty(Source, context, static_cast(param), fvals); @@ -2970,8 +3002,10 @@ try { *value2 = fvals[1]; *value3 = fvals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) @@ -2981,15 +3015,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3000,14 +3036,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) @@ -3017,9 +3055,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array dvals{}; GetProperty(Source, context, static_cast(param), dvals); @@ -3027,8 +3065,10 @@ try { *value2 = dvals[1]; *value3 = dvals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) @@ -3038,15 +3078,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3057,14 +3099,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) @@ -3074,18 +3118,20 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); + std::array ivals{}; GetProperty(Source, context, static_cast(param), ivals); *value1 = ivals[0]; *value2 = ivals[1]; *value3 = ivals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) @@ -3095,15 +3141,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3113,14 +3161,16 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) @@ -3130,9 +3180,9 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array i64vals{}; GetProperty(Source, context, static_cast(param), i64vals); @@ -3140,8 +3190,10 @@ try { *value2 = i64vals[1]; *value3 = i64vals[2]; } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) @@ -3151,15 +3203,17 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) - throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3169,12 +3223,14 @@ try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) @@ -3182,17 +3238,19 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, A ALint64SOFT start_time) noexcept try { if(start_time < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; + context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}, nanoseconds{start_time}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) @@ -3200,34 +3258,33 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; - std::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(sids.size() <= source_storage.size()) LIKELY - srchandles = al::span{source_storage}.first(sids.size()); - else + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(sids.size()); - srchandles = extra_sources; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; - std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), - [context](const ALuint sid) -> ALsource* - { - if(ALsource *src{LookupSource(context, sid)}) - return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; - }); + auto lookup_src = [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) @@ -3235,37 +3292,36 @@ FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, const ALuint *sources, ALint64SOFT start_time) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; if(start_time < 0) - throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; + context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); al::span sids{sources, static_cast(n)}; - std::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(sids.size() <= source_storage.size()) LIKELY - srchandles = al::span{source_storage}.first(sids.size()); - else + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(sids.size()); - srchandles = extra_sources; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; - std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), - [context](const ALuint sid) -> ALsource* - { - if(ALsource *src{LookupSource(context, sid)}) - return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; - }); + auto lookup_src = [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles, nanoseconds{start_time}); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3278,29 +3334,26 @@ FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Pausing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; - std::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(sids.size() <= source_storage.size()) LIKELY - srchandles = al::span{source_storage}.first(sids.size()); - else + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(sids.size()); - srchandles = extra_sources; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; - std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), - [context](const ALuint sid) -> ALsource* - { - if(ALsource *src{LookupSource(context, sid)}) - return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; - }); + auto lookup_src = [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, chamge the voice (asynchronously) to @@ -3340,8 +3393,10 @@ try { } } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3354,29 +3409,26 @@ FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Stopping {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; - std::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(sids.size() <= source_storage.size()) LIKELY - srchandles = al::span{source_storage}.first(sids.size()); - else + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(sids.size()); - srchandles = extra_sources; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; - std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), - [context](const ALuint sid) -> ALsource* - { - if(ALsource *src{LookupSource(context, sid)}) - return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; - }); + auto lookup_src = [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) @@ -3403,8 +3455,10 @@ try { if(tail) LIKELY SendVoiceChanges(context, tail); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3417,29 +3471,26 @@ FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei const ALuint *sources) noexcept try { if(n < 0) - throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n}; + context->throw_error(AL_INVALID_VALUE, "Rewinding {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; - std::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(sids.size() <= source_storage.size()) LIKELY - srchandles = al::span{source_storage}.first(sids.size()); - else + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(sids.size()); - srchandles = extra_sources; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; - std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), - [context](const ALuint sid) -> ALsource* - { - if(ALsource *src{LookupSource(context, sid)}) - return src; - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; - }); + auto lookup_src = [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) @@ -3468,8 +3519,10 @@ try { if(tail) LIKELY SendVoiceChanges(context, tail); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3478,20 +3531,20 @@ FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALu ALsizei nb, const ALuint *buffers) noexcept try { if(nb < 0) - throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb}; + context->throw_error(AL_INVALID_VALUE, "Queueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); /* Can't queue on a Static Source */ if(source->SourceType == AL_STATIC) - throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src}; + context->throw_error(AL_INVALID_OPERATION, "Queueing onto static source {}", src); /* Check for a valid Buffer, for its frequency and format */ - ALCdevice *device{context->mALDevice.get()}; + auto *device = context->mALDevice.get(); ALbuffer *BufferFmt{nullptr}; for(auto &item : source->mQueue) { @@ -3504,26 +3557,25 @@ try { const size_t NewListStart{source->mQueue.size()}; try { ALbufferQueueItem *BufferList{nullptr}; - std::for_each(bids.cbegin(), bids.cend(), - [source,device,&BufferFmt,&BufferList](const ALuint bid) + auto append_buffer = [context,source,device,&BufferFmt,&BufferList](const ALuint bid) { ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; if(bid && !buffer) - throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid}; + context->throw_error(AL_INVALID_NAME, "Queueing invalid buffer ID {}", bid); if(buffer) { if(buffer->mSampleRate < 1) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing buffer %u with no format", buffer->id}; + context->throw_error(AL_INVALID_OPERATION, + "Queueing buffer {} with no format", buffer->id); if(buffer->mCallback) - throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u", - buffer->id}; + context->throw_error(AL_INVALID_OPERATION, "Queueing callback buffer {}", + buffer->id); if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing non-persistently mapped buffer %u", buffer->id}; + context->throw_error(AL_INVALID_OPERATION, + "Queueing non-persistently mapped buffer {}", buffer->id); } source->mQueue.emplace_back(); @@ -3543,11 +3595,11 @@ try { BufferList->mBuffer = buffer; IncrementRef(buffer->ref); - bool fmt_mismatch{false}; 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; @@ -3557,15 +3609,16 @@ try { 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)); } - if(fmt_mismatch) - throw al::context_error{AL_INVALID_OPERATION, - "Queueing buffer with mismatched format\n" - " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, - NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), - buffer->mSampleRate, NameFromFormat(buffer->mType), - NameFromFormat(buffer->mChannels)}; - }); + }; + std::for_each(bids.cbegin(), bids.cend(), append_buffer); } catch(...) { /* A buffer failed (invalid ID or format), or there was some other @@ -3592,8 +3645,10 @@ try { (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release); } } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) @@ -3601,18 +3656,18 @@ FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, A ALsizei nb, ALuint *buffers) noexcept try { if(nb < 0) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); if(source->SourceType != AL_STREAMING) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing from a non-streaming source {}", src); if(source->Looping) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing from looping source {}", src); /* Make sure enough buffers have been processed to unqueue. */ const al::span bids{buffers, static_cast(nb)}; @@ -3630,8 +3685,8 @@ try { } } if(processed < bids.size()) - throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)", - nb, (nb==1)?"":"s", processed}; + context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffer{} (only {} processed)", + nb, (nb==1) ? "" : "s", processed); std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint { @@ -3646,8 +3701,10 @@ try { return bid; }); } -catch(al::context_error& e) { - context->setError(e.errorCode(), "%s", e.what()); +catch(al::base_exception&) { +} +catch(std::exception &e) { + ERR("Caught exception: {}", e.what()); } @@ -3715,7 +3772,7 @@ void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name) auto source = LookupSource(context, id); if(!source) - throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id}; + context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", id); context->mSourceNames.insert_or_assign(id, name); } @@ -3739,7 +3796,7 @@ SourceSubList::~SourceSubList() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX void ALsource::eaxInitialize(ALCcontext *context) noexcept { assert(context != nullptr); @@ -3753,11 +3810,6 @@ void ALsource::eaxInitialize(ALCcontext *context) noexcept mEaxChanged = true; } -void ALsource::eaxDispatch(const EaxCall& call) -{ - call.is_get() ? eax_get(call) : eax_set(call); -} - ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept { return LookupSource(&al_context, source_id); @@ -3805,7 +3857,7 @@ void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) } } -void ALsource::eax1_set_defaults(Eax1Props& props) noexcept +void ALsource::eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept { props.fMix = EAX_REVERBMIX_USEDISTANCE; } @@ -3816,7 +3868,7 @@ void ALsource::eax1_set_defaults() noexcept mEax1.d = mEax1.i; } -void ALsource::eax2_set_defaults(Eax2Props& props) noexcept +void ALsource::eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; @@ -3839,7 +3891,7 @@ void ALsource::eax2_set_defaults() noexcept mEax2.d = mEax2.i; } -void ALsource::eax3_set_defaults(Eax3Props& props) noexcept +void ALsource::eax3_set_defaults(EAX30SOURCEPROPERTIES& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; @@ -3887,7 +3939,7 @@ void ALsource::eax4_set_defaults() noexcept void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept { - eax3_set_defaults(static_cast(props)); + eax3_set_defaults(static_cast(props)); props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; } @@ -3934,7 +3986,7 @@ void ALsource::eax_set_defaults() noexcept eax5_set_defaults(); } -void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept +void ALsource::eax1_translate(const EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept { eax5_set_defaults(dst); @@ -3951,7 +4003,7 @@ void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept } } -void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept +void ALsource::eax2_translate(const EAX20BUFFERPROPERTIES& src, Eax5Props& dst) noexcept { // Source. // @@ -3982,11 +4034,11 @@ void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept eax5_set_speaker_levels_defaults(dst.speaker_levels); } -void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept +void ALsource::eax3_translate(const EAX30SOURCEPROPERTIES& src, Eax5Props& dst) noexcept { // Source. // - static_cast(dst.source) = src; + static_cast(dst.source) = src; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Set everything else to defaults. @@ -4000,7 +4052,7 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept { // Source. // - static_cast(dst.source) = src.source; + static_cast(dst.source) = src.source; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Sends. @@ -4012,32 +4064,30 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept // Active FX slots. // - for(size_t i{0};i < EAX50_MAX_ACTIVE_FXSLOTS;++i) + auto translate_slotid = [](const GUID &src_id) -> GUID { - auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i]; + 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; - if(i < EAX40_MAX_ACTIVE_FXSLOTS) - { - const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i]; - - if(src_id == EAX_NULL_GUID) - dst_id = EAX_NULL_GUID; - else if(src_id == EAX_PrimaryFXSlotID) - dst_id = EAX_PrimaryFXSlotID; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot0) - dst_id = EAXPROPERTYID_EAX50_FXSlot0; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot1) - dst_id = EAXPROPERTYID_EAX50_FXSlot1; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot2) - dst_id = EAXPROPERTYID_EAX50_FXSlot2; - else if(src_id == EAXPROPERTYID_EAX40_FXSlot3) - dst_id = EAXPROPERTYID_EAX50_FXSlot3; - else - assert(false && "Unknown active FX slot ID."); - } - else - dst_id = EAX_NULL_GUID; - } + UNLIKELY + ERR("Unexpected active FX slot ID"); + return EAX_NULL_GUID; + }; + const auto src_slots = al::span{src.active_fx_slots.guidActiveFXSlots}; + const auto dst_slots = al::span{dst.active_fx_slots.guidActiveFXSlots}; + auto dstiter = std::transform(src_slots.cbegin(), src_slots.cend(), dst_slots.begin(), + translate_slotid); + std::fill(dstiter, dst_slots.end(), EAX_NULL_GUID); // Speaker levels. // @@ -4058,60 +4108,55 @@ float ALsource::eax_calculate_dst_occlusion_mb( EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept { - auto gain_mb = - static_cast(mEax.source.lDirect) + - (static_cast(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) + - eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionDirectRatio, - mEax.source.flOcclusionLFRatio); + const auto &source = mEax.source; - const auto has_source_occlusion = (mEax.source.lOcclusion != 0); - - auto gain_hf_mb = - static_cast(mEax.source.lDirectHF) + - static_cast(mEax.source.lObstruction); + auto gain_mb = static_cast(source.lObstruction) * source.flObstructionLFRatio; + auto gainhf_mb = static_cast(source.lObstruction); for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; - if(has_source_occlusion) + if(source.lOcclusion != 0) { const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); - const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); + const auto is_environmental_fx = ((fx_slot_eax.ulFlags&EAXFXSLOTFLAGS_ENVIRONMENT) != 0); const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); - const auto is_listener_environment = (is_environmental_fx && is_primary); - if(is_listener_environment) + if(is_environmental_fx && is_primary) { - gain_mb += eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionDirectRatio, - mEax.source.flOcclusionLFRatio); + gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, + source.flOcclusionDirectRatio, source.flOcclusionLFRatio); - gain_hf_mb += static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio; + gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionDirectRatio; } } const auto& send = mEax.sends[i]; - if(send.lOcclusion != 0) { - gain_mb += eax_calculate_dst_occlusion_mb( - send.lOcclusion, - send.flOcclusionDirectRatio, + gain_mb += eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionDirectRatio, send.flOcclusionLFRatio); - gain_hf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; + gainhf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; } } - return EaxAlLowPassParam{ - level_mb_to_gain(gain_mb), - std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; + /* gainhf_mb is the absolute mBFS of the filter's high-frequency volume, + * and gain_mb is the absolute mBFS of the filter's low-frequency volume. + * Adjust the HF volume to be relative to the LF volume, to make the + * appropriate main and relative HF filter volumes. + * + * Also add the Direct and DirectHF properties to the filter, which are + * already the main and relative HF volumes. + */ + gainhf_mb -= gain_mb - static_cast(source.lDirectHF); + gain_mb += static_cast(source.lDirect); + + return EaxAlLowPassParam{level_mb_to_gain(gain_mb), + std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } EaxAlLowPassParam ALsource::eax_create_room_filter_param( @@ -4119,42 +4164,40 @@ EaxAlLowPassParam ALsource::eax_create_room_filter_param( const EAXSOURCEALLSENDPROPERTIES& send) const noexcept { const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); - const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); - const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); - const auto is_listener_environment = (is_environmental_fx && is_primary); + const auto is_environmental_fx = bool{(fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0}; + const auto is_primary = bool{mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()}; - const auto gain_mb = - (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + - static_cast((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) + - (is_listener_environment ? - eax_calculate_dst_occlusion_mb( - mEax.source.lOcclusion, - mEax.source.flOcclusionRoomRatio, - mEax.source.flOcclusionLFRatio) : - 0.0f) + - eax_calculate_dst_occlusion_mb( - send.lOcclusion, - send.flOcclusionRoomRatio, - send.flOcclusionLFRatio) + - (is_listener_environment ? - (static_cast(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) : - 0.0f) + - (static_cast(send.lExclusion) * send.flExclusionLFRatio); + auto gain_mb = (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + + eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionRoomRatio, + send.flOcclusionLFRatio) + + (static_cast(send.lExclusion) * send.flExclusionLFRatio); - const auto gain_hf_mb = - static_cast(fx_slot_eax.lOcclusion) + - static_cast((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) + - (is_listener_environment ? - ((static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) : - 0.0f) + - (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + - (is_listener_environment ? - static_cast(mEax.source.lExclusion + send.lExclusion) : - 0.0f); + auto gainhf_mb = static_cast(fx_slot_eax.lOcclusion) + + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio); - return EaxAlLowPassParam{ - level_mb_to_gain(gain_mb), - std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; + if(is_environmental_fx && is_primary) + { + const auto &source = mEax.source; + + gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, source.flOcclusionRoomRatio, + source.flOcclusionLFRatio); + gain_mb += static_cast(source.lExclusion) * source.flExclusionLFRatio; + + gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionRoomRatio; + gainhf_mb += static_cast(source.lExclusion + send.lExclusion); + } + + gainhf_mb -= gain_mb - static_cast(send.lSendHF); + gain_mb += static_cast(send.lSend); + if(is_environmental_fx) + { + const auto &source = mEax.source; + gain_mb += static_cast(source.lRoom); + gainhf_mb += static_cast(source.lRoomHF); + } + + return EaxAlLowPassParam{level_mb_to_gain(gain_mb), + std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } void ALsource::eax_update_direct_filter() @@ -4225,7 +4268,7 @@ void ALsource::eax_set_efx_wet_gain_hf_auto() WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0); } -void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) +void ALsource::eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: @@ -4241,7 +4284,7 @@ void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) } } -void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) +void ALsource::eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: @@ -4308,7 +4351,7 @@ void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) } } -void ALsource::eax3_set(const EaxCall& call, Eax3Props& props) +void ALsource::eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: @@ -4513,10 +4556,13 @@ void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: - case EAXSOURCE_FLAGS: eax3_set(call, props.source); break; + case EAXSOURCE_FLAGS: + eax_defer(call, props.source.ulFlags); + break; + case EAXSOURCE_SENDPARAMETERS: eax5_defer_sends(call, props.sends); break; @@ -4570,16 +4616,14 @@ void ALsource::eax_set(const EaxCall& call) mEaxVersion = eax_version; } -void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count) +void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids) { - assert(ids != nullptr); - assert(max_count == EAX40_MAX_ACTIVE_FXSLOTS || max_count == EAX50_MAX_ACTIVE_FXSLOTS); - const auto dst_ids = call.get_values(max_count); - const auto count = dst_ids.size(); - std::uninitialized_copy_n(ids, count, dst_ids.begin()); + assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS); + const auto dst_ids = call.get_values(src_ids.size()); + std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin()); } -void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) +void ALsource::eax1_get(const EaxCall& call, const EAXBUFFER_REVERBPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: @@ -4592,7 +4636,7 @@ void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) } } -void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props) +void ALsource::eax2_get(const EaxCall& call, const EAX20BUFFERPROPERTIES& props) { switch (call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: @@ -4659,25 +4703,25 @@ void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props) } } -void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get_obstruction(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { const auto& subprops = reinterpret_cast(props.lObstruction); call.set_value(subprops); } -void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get_occlusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { const auto& subprops = reinterpret_cast(props.lOcclusion); call.set_value(subprops); } -void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get_exclusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { const auto& subprops = reinterpret_cast(props.lExclusion); call.set_value(subprops); } -void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props) +void ALsource::eax3_get(const EaxCall& call, const EAX30SOURCEPROPERTIES& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: @@ -4824,8 +4868,7 @@ void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots.data(), - EAX40_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; default: @@ -4897,8 +4940,7 @@ void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots.data(), - EAX50_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; case EAXSOURCE_MACROFXFACTOR: diff --git a/libs/openal-soft/al/source.h b/libs/openal-soft/al/source.h index 7c81e864..7d56e3a5 100644 --- a/libs/openal-soft/al/source.h +++ b/libs/openal-soft/al/source.h @@ -1,6 +1,8 @@ #ifndef AL_SOURCE_H #define AL_SOURCE_H +#include "config.h" + #include #include #include @@ -20,7 +22,7 @@ #include "core/context.h" #include "core/voice.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/exception.h" @@ -45,12 +47,10 @@ inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; - - DISABLE_ALLOC }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX class EaxSourceException : public EaxException { public: explicit EaxSourceException(const char* message) @@ -71,7 +71,7 @@ struct ALsource { float RefDistance{1.0f}; float MaxDistance{std::numeric_limits::max()}; float RolloffFactor{1.0f}; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to // AL_ROLLOFF_FACTOR float RolloffFactor2{0.0f}; @@ -165,10 +165,10 @@ struct ALsource { DISABLE_ALLOC -#ifdef ALSOFT_EAX +#if ALSOFT_EAX public: void eaxInitialize(ALCcontext *context) noexcept; - void eaxDispatch(const EaxCall& call); + void eaxDispatch(const EaxCall& call) { call.is_get() ? eax_get(call) : eax_set(call); } void eaxCommit(); void eaxMarkAsChanged() noexcept { mEaxChanged = true; } @@ -199,26 +199,23 @@ private: using EaxSpeakerLevels = std::array; using EaxSends = std::array; - using Eax1Props = EAXBUFFER_REVERBPROPERTIES; struct Eax1State { - Eax1Props i; // Immediate. - Eax1Props d; // Deferred. + EAXBUFFER_REVERBPROPERTIES i; // Immediate. + EAXBUFFER_REVERBPROPERTIES d; // Deferred. }; - using Eax2Props = EAX20BUFFERPROPERTIES; struct Eax2State { - Eax2Props i; // Immediate. - Eax2Props d; // Deferred. + EAX20BUFFERPROPERTIES i; // Immediate. + EAX20BUFFERPROPERTIES d; // Deferred. }; - using Eax3Props = EAX30SOURCEPROPERTIES; struct Eax3State { - Eax3Props i; // Immediate. - Eax3Props d; // Deferred. + EAX30SOURCEPROPERTIES i; // Immediate. + EAX30SOURCEPROPERTIES d; // Deferred. }; struct Eax4Props { - Eax3Props source; + EAX30SOURCEPROPERTIES source; EaxSends sends; EAX40ACTIVEFXSLOTS active_fx_slots; }; @@ -490,14 +487,14 @@ private: }; struct Eax1SourceAllValidator { - void operator()(const Eax1Props& props) const + void operator()(const EAXBUFFER_REVERBPROPERTIES& props) const { Eax1SourceReverbMixValidator{}(props.fMix); } }; struct Eax2SourceAllValidator { - void operator()(const Eax2Props& props) const + void operator()(const EAX20BUFFERPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); @@ -516,7 +513,7 @@ private: }; struct Eax3SourceAllValidator { - void operator()(const Eax3Props& props) const + void operator()(const EAX30SOURCEPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); @@ -542,7 +539,24 @@ private: struct Eax5SourceAllValidator { void operator()(const EAX50SOURCEPROPERTIES& props) const { - Eax3SourceAllValidator{}(static_cast(props)); + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); + Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); + Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); + Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); + Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); + Eax5SourceFlagsValidator{}(props.ulFlags); Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor); } }; @@ -806,11 +820,11 @@ private: [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; - static void eax1_set_defaults(Eax1Props& props) noexcept; + static void eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept; void eax1_set_defaults() noexcept; - static void eax2_set_defaults(Eax2Props& props) noexcept; + static void eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept; void eax2_set_defaults() noexcept; - static void eax3_set_defaults(Eax3Props& props) 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; @@ -823,9 +837,9 @@ private: void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; - static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; - static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; - static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; + static void 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 float eax_calculate_dst_occlusion_mb( @@ -889,13 +903,13 @@ private: } } - static void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count); - static void eax1_get(const EaxCall& call, const Eax1Props& props); - static void eax2_get(const EaxCall& call, const Eax2Props& props); - static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); - static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); - static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); - static void eax3_get(const EaxCall& call, const Eax3Props& props); + static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids); + 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_obstruction(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get_occlusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get_exclusion(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); + static void eax3_get(const EaxCall& call, const EAX30SOURCEPROPERTIES& props); void eax4_get(const EaxCall& call, const Eax4Props& props); static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); @@ -1017,9 +1031,9 @@ private: void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_hf_auto(); - static void eax1_set(const EaxCall& call, Eax1Props& props); - static void eax2_set(const EaxCall& call, Eax2Props& props); - void eax3_set(const EaxCall& call, Eax3Props& props); + static void eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props); + static void eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props); + void eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props); void eax4_set(const EaxCall& call, Eax4Props& props); static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); diff --git a/libs/openal-soft/al/state.cpp b/libs/openal-soft/al/state.cpp index a82ce8e4..e30b7a89 100644 --- a/libs/openal-soft/al/state.cpp +++ b/libs/openal-soft/al/state.cpp @@ -40,6 +40,7 @@ #include "al/listener.h" #include "alc/alu.h" #include "alc/context.h" +#include "alc/device.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "atomic.h" @@ -52,9 +53,7 @@ #include "opthelpers.h" #include "strutils.h" -#ifdef ALSOFT_EAX -#include "alc/device.h" - +#if ALSOFT_EAX #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX @@ -62,6 +61,8 @@ 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"; } @@ -82,8 +83,10 @@ 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"; } }; +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 @@ -92,6 +95,10 @@ 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"; } }; const ALchar *GetResamplerName(const Resampler rtype) { @@ -100,11 +107,14 @@ const ALchar *GetResamplerName(const Resampler rtype) { HANDLE_RESAMPLER(Resampler::Point); HANDLE_RESAMPLER(Resampler::Linear); - HANDLE_RESAMPLER(Resampler::Cubic); + 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. */ @@ -141,24 +151,24 @@ constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum } enum PropertyValue : ALenum { - DopplerFactor = AL_DOPPLER_FACTOR, - DopplerVelocity = AL_DOPPLER_VELOCITY, - DistanceModel = AL_DISTANCE_MODEL, - SpeedOfSound = AL_SPEED_OF_SOUND, - DeferredUpdates = AL_DEFERRED_UPDATES_SOFT, - GainLimit = AL_GAIN_LIMIT_SOFT, - NumResamplers = AL_NUM_RESAMPLERS_SOFT, - DefaultResampler = AL_DEFAULT_RESAMPLER_SOFT, - DebugLoggedMessages = AL_DEBUG_LOGGED_MESSAGES_EXT, - DebugNextLoggedMessageLength = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, - MaxDebugMessageLength = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, - MaxDebugLoggedMessages = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, - MaxDebugGroupDepth = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, - MaxLabelLength = AL_MAX_LABEL_LENGTH_EXT, - ContextFlags = AL_CONTEXT_FLAGS_EXT, -#ifdef ALSOFT_EAX - EaxRamSize = AL_EAX_RAM_SIZE, - EaxRamFree = AL_EAX_RAM_FREE, + 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 }; @@ -256,7 +266,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(context->mContextFlags.to_ulong()); return; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #define EAX_ERROR "[alGetInteger] EAX not enabled" case AL_EAX_RAM_SIZE: @@ -265,7 +275,7 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(eax_x_ram_max_size); return; } - ERR(EAX_ERROR "\n"); + ERR(EAX_ERROR); break; case AL_EAX_RAM_FREE: @@ -276,13 +286,13 @@ void GetValue(ALCcontext *context, ALenum pname, T *values) *values = cast_value(device->eax_x_ram_free_size); return; } - ERR(EAX_ERROR "\n"); + ERR(EAX_ERROR); break; #undef EAX_ERROR #endif // ALSOFT_EAX } - context->setError(AL_INVALID_ENUM, "Invalid context property 0x%04x", pname); + context->setError(AL_INVALID_ENUM, "Invalid context property {:#04x}", as_unsigned(pname)); } @@ -328,7 +338,8 @@ FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capabili 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 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid enable property {:#04x}", + as_unsigned(capability)); } AL_API DECL_FUNC1(void, alDisable, ALenum,capability) @@ -352,7 +363,8 @@ FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capabil context->mStopVoicesOnDisconnect.store(false); return; } - context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid disable property {:#04x}", + as_unsigned(capability)); } AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) @@ -366,14 +378,15 @@ FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; } - context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + context->setError(AL_INVALID_VALUE, "Invalid is enabled property {:#04x}", + as_unsigned(capability)); return AL_FALSE; } #define DECL_GETFUNC(R, Name, Ext) \ -AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ +auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ { \ - R value{}; \ + auto value = R{}; \ auto context = GetContextRef(); \ if(!context) UNLIKELY return value; \ Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ @@ -381,18 +394,19 @@ AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ { \ - R value{}; \ + auto value = R{}; \ Name##vDirect##Ext(context, pname, &value); \ return value; \ } -DECL_GETFUNC(ALboolean, alGetBoolean,) -DECL_GETFUNC(ALdouble, alGetDouble,) -DECL_GETFUNC(ALfloat, alGetFloat,) -DECL_GETFUNC(ALint, alGetInteger,) +AL_API DECL_GETFUNC(ALboolean, alGetBoolean,) +AL_API DECL_GETFUNC(ALdouble, alGetDouble,) +AL_API DECL_GETFUNC(ALfloat, alGetFloat,) +AL_API DECL_GETFUNC(ALint, alGetInteger,) -DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) -DECL_GETFUNC(ALvoid*, alGetPointer,SOFT) +DECL_GETFUNC(ALvoidptr, alGetPointer,EXT) +AL_API DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) +AL_API DECL_GETFUNC(ALvoidptr, alGetPointer,SOFT) #undef DECL_GETFUNC @@ -439,6 +453,10 @@ FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALen AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept +{ return alGetPointervDirectEXT(context, pname, values); } + +FORCE_ALIGN DECL_FUNCEXT2(void, alGetPointerv,EXT, ALenum,pname, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); @@ -461,7 +479,8 @@ FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum *values = context->mDebugParam; return; } - context->setError(AL_INVALID_ENUM, "Invalid context pointer property 0x%04x", pname); + context->setError(AL_INVALID_ENUM, "Invalid context pointer property {:#04x}", + as_unsigned(pname)); } AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) @@ -469,9 +488,18 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe { switch(pname) { - case AL_VENDOR: return GetVendorString(); - case AL_VERSION: return GetVersionString(); - case AL_RENDERER: return GetRendererString(); + case AL_VENDOR: + if(auto device = context->mALDevice.get(); !device->mVendorOverride.empty()) + return device->mVendorOverride.c_str(); + return GetVendorString(); + case AL_VERSION: + if(auto device = context->mALDevice.get(); !device->mVersionOverride.empty()) + return device->mVersionOverride.c_str(); + return GetVersionString(); + case AL_RENDERER: + if(auto device = context->mALDevice.get(); !device->mRendererOverride.empty()) + return device->mRendererOverride.c_str(); + return GetRendererString(); case AL_EXTENSIONS: return context->mExtensionsString.c_str(); case AL_NO_ERROR: return GetNoErrorString(); case AL_INVALID_NAME: return GetInvalidNameString(); @@ -482,7 +510,7 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALe case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); } - context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + context->setError(AL_INVALID_VALUE, "Invalid string property {:#04x}", as_unsigned(pname)); return nullptr; } @@ -490,7 +518,7 @@ AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value >= 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); + context->setError(AL_INVALID_VALUE, "Doppler factor {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -503,7 +531,7 @@ AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value > 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); + context->setError(AL_INVALID_VALUE, "Speed of sound {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -523,7 +551,8 @@ FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum v UpdateProps(context); } else - context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); + context->setError(AL_INVALID_VALUE, "Distance model {:#04x} out of range", + as_unsigned(value)); } @@ -548,12 +577,13 @@ FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context switch(pname) { case AL_RESAMPLER_NAME_SOFT: - if(index >= 0 && index <= static_cast(Resampler::Max)) + if(index >= 0 && index <= al::to_underlying(Resampler::Max)) return GetResamplerName(static_cast(index)); - context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); + context->setError(AL_INVALID_VALUE, "Resampler name index {} out of range", index); return nullptr; } - context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); + context->setError(AL_INVALID_VALUE, "Invalid string indexed property {:#04x}", + as_unsigned(pname)); return nullptr; } @@ -564,13 +594,13 @@ AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept if(!context) UNLIKELY return; if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY - context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 1, DebugSeverity::Medium, "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); if(!(value >= 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); + context->setError(AL_INVALID_VALUE, "Doppler velocity {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; @@ -608,6 +638,9 @@ void UpdateContextProps(ALCcontext *context) 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; diff --git a/libs/openal-soft/alc/alc.cpp b/libs/openal-soft/alc/alc.cpp index 58f8dd0b..b55fce80 100644 --- a/libs/openal-soft/alc/alc.cpp +++ b/libs/openal-soft/alc/alc.cpp @@ -19,6 +19,8 @@ */ #include "config.h" +#include "config_backends.h" +#include "config_simd.h" #include "version.h" @@ -34,15 +36,14 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include +#include #include #include #include @@ -52,7 +53,8 @@ #include #include #include -#include +#include +#include #include #include @@ -66,12 +68,12 @@ #include "al/debug.h" #include "al/effect.h" #include "al/filter.h" -#include "al/listener.h" #include "al/source.h" #include "alc/events.h" #include "albit.h" #include "alconfig.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" @@ -85,13 +87,12 @@ #include "core/cpu_caps.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" -#include "core/except.h" +#include "core/filters/nfc.h" #include "core/helpers.h" #include "core/mastering.h" -#include "core/mixer/hrtfdefs.h" #include "core/fpu_ctrl.h" -#include "core/front_stablizer.h" #include "core/logging.h" #include "core/uhjfilter.h" #include "core/voice.h" @@ -100,6 +101,7 @@ #include "effects/base.h" #include "export_list.h" #include "flexarray.h" +#include "fmt/core.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" @@ -108,59 +110,65 @@ #include "backends/base.h" #include "backends/null.h" #include "backends/loopback.h" -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE #include "backends/pipewire.h" #endif -#ifdef HAVE_JACK +#if HAVE_JACK #include "backends/jack.h" #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO #include "backends/pulseaudio.h" #endif -#ifdef HAVE_ALSA +#if HAVE_ALSA #include "backends/alsa.h" #endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI #include "backends/wasapi.h" #endif -#ifdef HAVE_COREAUDIO +#if HAVE_COREAUDIO #include "backends/coreaudio.h" #endif -#ifdef HAVE_OPENSL +#if HAVE_OPENSL #include "backends/opensl.h" #endif -#ifdef HAVE_OBOE +#if HAVE_OBOE #include "backends/oboe.h" #endif -#ifdef HAVE_SOLARIS +#if HAVE_SOLARIS #include "backends/solaris.h" #endif -#ifdef HAVE_SNDIO -#include "backends/sndio.h" +#if HAVE_SNDIO +#include "backends/sndio.hpp" #endif -#ifdef HAVE_OSS +#if HAVE_OSS #include "backends/oss.h" #endif -#ifdef HAVE_DSOUND +#if HAVE_DSOUND #include "backends/dsound.h" #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM #include "backends/winmm.h" #endif -#ifdef HAVE_PORTAUDIO -#include "backends/portaudio.h" +#if HAVE_PORTAUDIO +#include "backends/portaudio.hpp" #endif -#ifdef HAVE_SDL2 +#if HAVE_SDL3 +#include "backends/sdl3.h" +#endif +#if HAVE_SDL2 #include "backends/sdl2.h" #endif -#ifdef HAVE_WAVE +#if HAVE_OTHERIO +#include "backends/otherio.h" +#endif +#if HAVE_WAVE #include "backends/wave.h" #endif -#ifdef ALSOFT_EAX +#if ALSOFT_EAX +#include "al/eax/api.h" #include "al/eax/globals.h" -#include "al/eax/x_ram.h" -#endif // ALSOFT_EAX +#endif /************************************************ @@ -191,6 +199,15 @@ using voidp = void*; using float2 = std::array; +auto gProcessRunning = true; +struct ProcessWatcher { + ProcessWatcher() = default; + ProcessWatcher(const ProcessWatcher&) = delete; + ProcessWatcher& operator=(const ProcessWatcher&) = delete; + ~ProcessWatcher() { gProcessRunning = false; } +}; +ProcessWatcher gProcessWatcher; + /************************************************ * Backends ************************************************/ @@ -200,54 +217,60 @@ struct BackendInfo { }; std::array BackendList{ -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE BackendInfo{"pipewire", PipeWireBackendFactory::getFactory}, #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO BackendInfo{"pulse", PulseBackendFactory::getFactory}, #endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI BackendInfo{"wasapi", WasapiBackendFactory::getFactory}, #endif -#ifdef HAVE_COREAUDIO +#if HAVE_COREAUDIO BackendInfo{"core", CoreAudioBackendFactory::getFactory}, #endif -#ifdef HAVE_OBOE +#if HAVE_OBOE BackendInfo{"oboe", OboeBackendFactory::getFactory}, #endif -#ifdef HAVE_OPENSL +#if HAVE_OPENSL BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif -#ifdef HAVE_ALSA +#if HAVE_ALSA BackendInfo{"alsa", AlsaBackendFactory::getFactory}, #endif -#ifdef HAVE_SOLARIS +#if HAVE_SOLARIS BackendInfo{"solaris", SolarisBackendFactory::getFactory}, #endif -#ifdef HAVE_SNDIO +#if HAVE_SNDIO BackendInfo{"sndio", SndIOBackendFactory::getFactory}, #endif -#ifdef HAVE_OSS +#if HAVE_OSS BackendInfo{"oss", OSSBackendFactory::getFactory}, #endif -#ifdef HAVE_JACK - BackendInfo{"jack", JackBackendFactory::getFactory}, -#endif -#ifdef HAVE_DSOUND +#if HAVE_DSOUND BackendInfo{"dsound", DSoundBackendFactory::getFactory}, #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM BackendInfo{"winmm", WinMMBackendFactory::getFactory}, #endif -#ifdef HAVE_PORTAUDIO +#if HAVE_PORTAUDIO BackendInfo{"port", PortBackendFactory::getFactory}, #endif -#ifdef HAVE_SDL2 +#if HAVE_SDL3 + BackendInfo{"sdl3", SDL3BackendFactory::getFactory}, +#endif +#if HAVE_SDL2 BackendInfo{"sdl2", SDL2BackendFactory::getFactory}, #endif +#if HAVE_JACK + BackendInfo{"jack", JackBackendFactory::getFactory}, +#endif +#if HAVE_OTHERIO + BackendInfo{"otherio", OtherIOBackendFactory::getFactory}, +#endif BackendInfo{"null", NullBackendFactory::getFactory}, -#ifdef HAVE_WAVE +#if HAVE_WAVE BackendInfo{"wave", WaveBackendFactory::getFactory}, #endif }; @@ -265,11 +288,19 @@ BackendFactory *CaptureFactory{}; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OpenAL Soft\0"; } +#ifdef _WIN32 +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } +#else +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return std::string_view{}; } +#endif + /************************************************ * Global variables ************************************************/ /* Enumerated device names */ +std::vector alcAllDevicesArray; +std::vector alcCaptureDeviceArray; std::string alcAllDevicesList; std::string alcCaptureDeviceList; @@ -297,9 +328,9 @@ constexpr uint DitherRNGSeed{22222u}; /************************************************ * ALC information ************************************************/ -[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> std::string_view +[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> const char* { - return "ALC_ENUMERATE_ALL_EXT " + return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " "ALC_EXT_direct_context " @@ -308,9 +339,9 @@ constexpr uint DitherRNGSeed{22222u}; "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_reopen_device " - "ALC_SOFT_system_events"sv; + "ALC_SOFT_system_events"; } -[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> const char* { return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " @@ -329,7 +360,7 @@ constexpr uint DitherRNGSeed{22222u}; "ALC_SOFT_output_mode " "ALC_SOFT_pause_device " "ALC_SOFT_reopen_device " - "ALC_SOFT_system_events"sv; + "ALC_SOFT_system_events"; } constexpr int alcMajorVersion{1}; @@ -339,13 +370,13 @@ constexpr int alcEFXMajorVersion{1}; constexpr int alcEFXMinorVersion{0}; -using DeviceRef = al::intrusive_ptr; +using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ -std::vector DeviceList; +std::vector DeviceList; std::vector ContextList; std::recursive_mutex ListLock; @@ -372,7 +403,7 @@ void alc_initconfig() else { auto u8name = wstr_to_utf8(*logfile); - ERR("Failed to open log file '%s'\n", u8name.c_str()); + ERR("Failed to open log file '{}'", u8name); } } #else @@ -380,11 +411,11 @@ void alc_initconfig() { FILE *logf{fopen(logfile->c_str(), "wt")}; if(logf) gLogFile = logf; - else ERR("Failed to open log file '%s'\n", logfile->c_str()); + else ERR("Failed to open log file '{}'", *logfile); } #endif - TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, + TRACE("Initializing library v{}-{} {}", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); { std::string names; @@ -400,7 +431,7 @@ void alc_initconfig() names += backend.name; } } - TRACE("Supported backends: %s\n", names.c_str()); + TRACE("Supported backends: {}", names); } ReadALConfig(); @@ -409,23 +440,23 @@ void alc_initconfig() if(al::case_compare(*suspendmode, "ignore"sv) == 0) { SuspendDefers = false; - TRACE("Selected context suspend behavior, \"ignore\"\n"); + TRACE("Selected context suspend behavior, \"ignore\""); } else - ERR("Unhandled context suspend behavior setting: \"%s\"\n", suspendmode->c_str()); + ERR("Unhandled context suspend behavior setting: \"{}\"", *suspendmode); } int capfilter{0}; -#if defined(HAVE_SSE4_1) +#if HAVE_SSE4_1 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; -#elif defined(HAVE_SSE3) +#elif HAVE_SSE3 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) +#elif HAVE_SSE2 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) +#elif HAVE_SSE capfilter |= CPU_CAP_SSE; #endif -#ifdef HAVE_NEON +#if HAVE_NEON capfilter |= CPU_CAP_NEON; #endif if(auto cpuopt = ConfigValueStr({}, {}, "disable-cpu-exts"sv)) @@ -460,24 +491,24 @@ void alc_initconfig() else if(al::case_compare(entry, "neon"sv) == 0) capfilter &= ~CPU_CAP_NEON; else - WARN("Invalid CPU extension \"%.*s\"\n", al::sizei(entry), entry.data()); + WARN("Invalid CPU extension \"{}\"", entry); } } if(auto cpuopt = GetCPUInfo()) { if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty()) { - TRACE("Vendor ID: \"%s\"\n", cpuopt->mVendor.c_str()); - TRACE("Name: \"%s\"\n", cpuopt->mName.c_str()); + TRACE("Vendor ID: \"{}\"", cpuopt->mVendor); + TRACE("Name: \"{}\"", cpuopt->mName); } const int caps{cpuopt->mCaps}; - TRACE("Extensions:%s%s%s%s%s%s\n", - ((capfilter&CPU_CAP_SSE) ? ((caps&CPU_CAP_SSE) ? " +SSE" : " -SSE") : ""), - ((capfilter&CPU_CAP_SSE2) ? ((caps&CPU_CAP_SSE2) ? " +SSE2" : " -SSE2") : ""), - ((capfilter&CPU_CAP_SSE3) ? ((caps&CPU_CAP_SSE3) ? " +SSE3" : " -SSE3") : ""), - ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""), - ((capfilter&CPU_CAP_NEON) ? ((caps&CPU_CAP_NEON) ? " +NEON" : " -NEON") : ""), - ((!capfilter) ? " -none-" : "")); + 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; } @@ -515,7 +546,7 @@ void alc_initconfig() else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjDecodeQuality = UhjQualityType::IIR; else - WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); + WARN("Unsupported uhj/decode-filter: {}", *uhjfiltopt); } if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) { @@ -526,7 +557,7 @@ void alc_initconfig() else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjEncodeQuality = UhjQualityType::IIR; else - WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); + WARN("Unsupported uhj/encode-filter: {}", *uhjfiltopt); } if(auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); traperr @@ -616,6 +647,20 @@ void alc_initconfig() if(endlist) BackendListEnd = backendlist_cur; } + else + { + /* Exclude the null and wave writer backends from being considered by + * default. This ensures there will be no available devices if none of + * the normal backends are usable, rather than pretending there is a + * device but outputs nowhere. + */ + while(BackendListEnd != BackendList.begin()) + { + --BackendListEnd; + if(BackendListEnd->name == "null"sv) + break; + } + } auto init_backend = [](BackendInfo &backend) -> void { @@ -625,20 +670,20 @@ void alc_initconfig() BackendFactory &factory = backend.getFactory(); if(!factory.init()) { - WARN("Failed to initialize backend \"%s\"\n", backend.name); + WARN("Failed to initialize backend \"{}\"", backend.name); return; } - TRACE("Initialized backend \"%s\"\n", backend.name); + TRACE("Initialized backend \"{}\"", backend.name); if(!PlaybackFactory && factory.querySupport(BackendType::Playback)) { PlaybackFactory = &factory; - TRACE("Added \"%s\" for playback\n", backend.name); + TRACE("Added \"{}\" for playback", backend.name); } if(!CaptureFactory && factory.querySupport(BackendType::Capture)) { CaptureFactory = &factory; - TRACE("Added \"%s\" for capture\n", backend.name); + TRACE("Added \"{}\" for capture", backend.name); } }; std::for_each(BackendList.begin(), BackendListEnd, init_backend); @@ -646,9 +691,9 @@ void alc_initconfig() LoopbackBackendFactory::getFactory().init(); if(!PlaybackFactory) - WARN("No playback backend available!\n"); + WARN("No playback backend available!"); if(!CaptureFactory) - WARN("No capture backend available!\n"); + WARN("No capture backend available!"); if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { @@ -673,27 +718,36 @@ void alc_initconfig() if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); if(defrevopt) LoadReverbPreset(*defrevopt, &ALCcontext::sDefaultEffect); -#ifdef ALSOFT_EAX +#if ALSOFT_EAX + if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) { - 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")) { - eax_g_is_enabled = *eax_enable_opt; - if(!eax_g_is_enabled) - TRACE("%s\n", "EAX disabled by a configuration."); + EaxTraceCommits = al::case_compare(*optval, "true"sv) == 0 + || strtol(optval->c_str(), nullptr, 0) == 1; } 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 %s disabled.\n", - (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) - ? "EAXReverb and Chorus are" : - DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is" : - DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is" : ""); - } + EaxTraceCommits = GetConfigValueBool({}, "eax"sv, "trace-commits"sv, false); } #endif // ALSOFT_EAX } @@ -710,12 +764,22 @@ void ProbeAllDevicesList() std::lock_guard listlock{ListLock}; if(!PlaybackFactory) + { + decltype(alcAllDevicesArray){}.swap(alcAllDevicesArray); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + } else { - std::string names{PlaybackFactory->probe(BackendType::Playback)}; - if(names.empty()) names += '\0'; - names.swap(alcAllDevicesList); + alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); + if(const auto prefix = GetDevicePrefix(); !prefix.empty()) + std::for_each(alcAllDevicesArray.begin(), alcAllDevicesArray.end(), + [prefix](std::string &name) { name.insert(0, prefix); }); + + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + if(alcAllDevicesArray.empty()) + alcAllDevicesList += '\0'; + else for(auto &devname : alcAllDevicesArray) + alcAllDevicesList.append(devname) += '\0'; } } void ProbeCaptureDeviceList() @@ -724,12 +788,22 @@ void ProbeCaptureDeviceList() std::lock_guard listlock{ListLock}; if(!CaptureFactory) + { + decltype(alcCaptureDeviceArray){}.swap(alcCaptureDeviceArray); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + } else { - std::string names{CaptureFactory->probe(BackendType::Capture)}; - if(names.empty()) names += '\0'; - names.swap(alcCaptureDeviceList); + alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); + if(const auto prefix = GetDevicePrefix(); !prefix.empty()) + std::for_each(alcCaptureDeviceArray.begin(), alcCaptureDeviceArray.end(), + [prefix](std::string &name) { name.insert(0, prefix); }); + + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + if(alcCaptureDeviceArray.empty()) + alcCaptureDeviceList += '\0'; + else for(auto &devname : alcCaptureDeviceArray) + alcCaptureDeviceList.append(devname) += '\0'; } } @@ -812,7 +886,7 @@ std::optional DevFmtTypeFromEnum(ALCenum type) case ALC_UNSIGNED_INT_SOFT: return DevFmtUInt; case ALC_FLOAT_SOFT: return DevFmtFloat; } - WARN("Unsupported format type: 0x%04x\n", type); + WARN("Unsupported format type: {:#04x}", as_unsigned(type)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) @@ -827,7 +901,7 @@ ALCenum EnumFromDevFmt(DevFmtType type) case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT; case DevFmtFloat: return ALC_FLOAT_SOFT; } - throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; + throw std::runtime_error{fmt::format("Invalid DevFmtType: {}", int{al::to_underlying(type)})}; } std::optional DevFmtChannelsFromEnum(ALCenum channels) @@ -842,7 +916,7 @@ std::optional DevFmtChannelsFromEnum(ALCenum channels) case ALC_7POINT1_SOFT: return DevFmtX71; case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } - WARN("Unsupported format channels: 0x%04x\n", channels); + WARN("Unsupported format channels: {:#04x}", as_unsigned(channels)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) @@ -858,9 +932,11 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; /* FIXME: Shouldn't happen. */ case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: break; } - throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; + throw std::runtime_error{fmt::format("Invalid DevFmtChannels: {}", + int{al::to_underlying(channels)})}; } std::optional DevAmbiLayoutFromEnum(ALCenum layout) @@ -870,7 +946,7 @@ std::optional DevAmbiLayoutFromEnum(ALCenum layout) case ALC_FUMA_SOFT: return DevAmbiLayout::FuMa; case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } - WARN("Unsupported ambisonic layout: 0x%04x\n", layout); + WARN("Unsupported ambisonic layout: {:#04x}", as_unsigned(layout)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiLayout layout) @@ -880,7 +956,8 @@ ALCenum EnumFromDevAmbi(DevAmbiLayout layout) case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT; case DevAmbiLayout::ACN: return ALC_ACN_SOFT; } - throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))}; + throw std::runtime_error{fmt::format("Invalid DevAmbiLayout: {}", + int{al::to_underlying(layout)})}; } std::optional DevAmbiScalingFromEnum(ALCenum scaling) @@ -891,7 +968,7 @@ std::optional DevAmbiScalingFromEnum(ALCenum scaling) case ALC_SN3D_SOFT: return DevAmbiScaling::SN3D; case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } - WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); + WARN("Unsupported ambisonic scaling: {:#04x}", as_unsigned(scaling)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) @@ -902,7 +979,8 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) case DevAmbiScaling::SN3D: return ALC_SN3D_SOFT; case DevAmbiScaling::N3D: return ALC_N3D_SOFT; } - throw std::runtime_error{"Invalid DevAmbiScaling: "+std::to_string(int(scaling))}; + throw std::runtime_error{fmt::format("Invalid DevAmbiScaling: {}", + int{al::to_underlying(scaling)})}; } @@ -962,13 +1040,9 @@ constexpr std::array X71Downmix{ }; -std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) +auto CreateDeviceLimiter(const al::Device *device, const float threshold) + -> std::unique_ptr { - static constexpr bool AutoKnee{true}; - static constexpr bool AutoAttack{true}; - static constexpr bool AutoRelease{true}; - static constexpr bool AutoPostGain{true}; - static constexpr bool AutoDeclip{true}; static constexpr float LookAheadTime{0.001f}; static constexpr float HoldTime{0.002f}; static constexpr float PreGainDb{0.0f}; @@ -978,9 +1052,12 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f static constexpr float AttackTime{0.02f}; static constexpr float ReleaseTime{0.2f}; - return Compressor::Create(device->RealOut.Buffer.size(), static_cast(device->Frequency), - AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, LookAheadTime, HoldTime, - PreGainDb, PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); + const auto flags = Compressor::FlagBits{}.set(Compressor::AutoKnee).set(Compressor::AutoAttack) + .set(Compressor::AutoRelease).set(Compressor::AutoPostGain).set(Compressor::AutoDeclip); + + return Compressor::Create(device->RealOut.Buffer.size(), + static_cast(device->mSampleRate), flags, LookAheadTime, HoldTime, PreGainDb, + PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); } /** @@ -989,15 +1066,23 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f * to jump forward or back. Must not be called while the device is running/ * mixing. */ -inline void UpdateClockBase(ALCdevice *device) +inline void UpdateClockBase(al::Device *device) { + using std::chrono::duration_cast; + const auto mixLock = device->getWriteMixLock(); - auto samplesDone = device->mSamplesDone.load(std::memory_order_relaxed); - auto clockBase = device->mClockBase.load(std::memory_order_relaxed); + 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; - clockBase += nanoseconds{seconds{samplesDone}} / device->Frequency; - device->mClockBase.store(clockBase, std::memory_order_relaxed); + 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); } @@ -1005,11 +1090,11 @@ inline void UpdateClockBase(ALCdevice *device) * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList) +auto UpdateDeviceParams(al::Device *device, const al::span attrList) -> ALCenum { if(attrList.empty() && device->Type == DeviceType::Loopback) { - WARN("Missing attributes for loopback device\n"); + WARN("Missing attributes for loopback device"); return ALC_INVALID_VALUE; } @@ -1063,12 +1148,11 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList TypeMap{"float32"sv, DevFmtFloat }, }; - const ALCchar *fmt{typeopt->c_str()}; auto iter = std::find_if(typelist.begin(), typelist.end(), - [svfmt=std::string_view{fmt}](const TypeMap &entry) -> bool + [svfmt=std::string_view{*typeopt}](const TypeMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == typelist.end()) - ERR("Unsupported sample-type: %s\n", fmt); + ERR("Unsupported sample-type: {}", *typeopt); else opttype = iter->type; } @@ -1087,6 +1171,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList ChannelMap{"surround61"sv, DevFmtX61, 0}, ChannelMap{"surround71"sv, DevFmtX71, 0}, ChannelMap{"surround714"sv, DevFmtX714, 0}, + ChannelMap{"surround7144"sv, DevFmtX7144, 0}, ChannelMap{"surround3d71"sv, DevFmtX3D71, 0}, ChannelMap{"surround51rear"sv, DevFmtX51, 0}, ChannelMap{"ambi1"sv, DevFmtAmbi3D, 1}, @@ -1094,12 +1179,11 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList ChannelMap{"ambi3"sv, DevFmtAmbi3D, 3}, }; - const ALCchar *fmt{chanopt->c_str()}; auto iter = std::find_if(chanlist.begin(), chanlist.end(), - [svfmt=std::string_view{fmt}](const ChannelMap &entry) -> bool + [svfmt=std::string_view{*chanopt}](const ChannelMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == chanlist.end()) - ERR("Unsupported channels: %s\n", fmt); + ERR("Unsupported channels: {}", *chanopt); else { optchans = iter->chans; @@ -1130,12 +1214,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList optscale = DevAmbiScaling::N3D; } else - ERR("Unsupported ambi-format: %s\n", ambiopt->c_str()); + ERR("Unsupported ambi-format: {}", *ambiopt); } if(auto hrtfopt = device->configValue({}, "hrtf"sv)) { - WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); + WARN("general/hrtf is deprecated, please use stereo-encoding instead"); if(al::case_compare(*hrtfopt, "true"sv) == 0) stereomode = StereoEncoding::Hrtf; @@ -1145,7 +1229,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList stereomode = StereoEncoding::Default; } else if(al::case_compare(*hrtfopt, "auto"sv) != 0) - ERR("Unexpected hrtf value: %s\n", hrtfopt->c_str()); + ERR("Unexpected hrtf value: {}", *hrtfopt); } } @@ -1158,7 +1242,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList else if(al::case_compare(*encopt, "hrtf") == 0) stereomode = StereoEncoding::Hrtf; else - ERR("Unexpected stereo-encoding: %s\n", encopt->c_str()); + ERR("Unexpected stereo-encoding: {}", *encopt); } // Check for app-specified attributes @@ -1168,17 +1252,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList std::optional opthrtf; int freqAttr{}; -#define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); +#define ATTRIBUTE(a) a: TRACE("{} = {}", #a, attrList[attrIdx + 1]); +#define ATTRIBUTE_HEX(a) a: TRACE("{} = {:#x}", #a, as_unsigned(attrList[attrIdx + 1])); for(size_t attrIdx{0};attrIdx < attrList.size();attrIdx+=2) { switch(attrList[attrIdx]) { - case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) + case ATTRIBUTE_HEX(ALC_FORMAT_CHANNELS_SOFT) if(device->Type == DeviceType::Loopback) optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; - case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) + case ATTRIBUTE_HEX(ALC_FORMAT_TYPE_SOFT) if(device->Type == DeviceType::Loopback) opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; @@ -1187,12 +1272,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList freqAttr = attrList[attrIdx + 1]; break; - case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) + case ATTRIBUTE_HEX(ALC_AMBISONIC_LAYOUT_SOFT) if(device->Type == DeviceType::Loopback) optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; - case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) + case ATTRIBUTE_HEX(ALC_AMBISONIC_SCALING_SOFT) if(device->Type == DeviceType::Loopback) optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; @@ -1214,7 +1299,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) numSends = static_cast(attrList[attrIdx + 1]); - if(numSends > std::numeric_limits::max()) numSends = 0; + if(numSends > uint{std::numeric_limits::max()}) numSends = 0; else numSends = std::min(numSends, uint{MaxSendCount}); break; @@ -1240,16 +1325,25 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList optlimit = std::nullopt; break; - case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) + case ATTRIBUTE_HEX(ALC_OUTPUT_MODE_SOFT) outmode = attrList[attrIdx + 1]; break; + case ATTRIBUTE_HEX(ALC_CONTEXT_FLAGS_EXT) + /* Handled in alcCreateContext */ + break; + + case ATTRIBUTE(ALC_SYNC) + /* Ignored attribute */ + break; + default: - TRACE("0x%04X = %d (0x%x)\n", attrList[attrIdx], - attrList[attrIdx + 1], attrList[attrIdx + 1]); + TRACE("{:#04x} = {} ({:#x})", as_unsigned(attrList[attrIdx]), + attrList[attrIdx + 1], as_unsigned(attrList[attrIdx + 1])); break; } } +#undef ATTRIBUTE_HEX #undef ATTRIBUTE if(device->Type == DeviceType::Loopback) @@ -1306,7 +1400,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList if(outmode != ALC_ANY_SOFT) { - using OutputMode = ALCdevice::OutputMode; + using OutputMode = al::Device::OutputMode; switch(OutputMode(outmode)) { case OutputMode::Any: break; @@ -1394,7 +1488,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList if(device->Type == DeviceType::Loopback) { - device->Frequency = *optsrate; + device->mSampleRate = *optsrate; device->FmtChans = *optchans; device->FmtType = *opttype; if(device->FmtChans == DevFmtAmbi3D) @@ -1410,9 +1504,9 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList device->FmtType = opttype.value_or(DevFmtTypeDefault); device->FmtChans = optchans.value_or(DevFmtChannelsDefault); device->mAmbiOrder = 0; - device->BufferSize = buffer_size; - device->UpdateSize = period_size; - device->Frequency = optsrate.value_or(DefaultOutputRate); + 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()); @@ -1426,20 +1520,20 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList && (device->mAmbiLayout == DevAmbiLayout::FuMa || device->mAmbiScale == DevAmbiScaling::FuMa)) { - ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + ERR("FuMa is incompatible with {}{} order ambisonics (up to 3rd order only)", device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); device->mAmbiOrder = 3; } } } - TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", + TRACE("Pre-reset: {}{}, {}{}, {}{}hz, {} / {} buffer", device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), - device->Flags.test(FrequencyRequest)?"*":"", device->Frequency, - device->UpdateSize, device->BufferSize); + device->Flags.test(FrequencyRequest)?"*":"", device->mSampleRate, + device->mUpdateSize, device->mBufferSize); - const uint oldFreq{device->Frequency}; + const uint oldFreq{device->mSampleRate}; const DevFmtChannels oldChans{device->FmtChans}; const DevFmtType oldType{device->FmtType}; try { @@ -1448,32 +1542,32 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { - ERR("Device error: %s\n", e.what()); - device->handleDisconnect("%s", e.what()); + 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 %s, got %s instead\n", DevFmtChannelsString(oldChans), + 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 %s, got %s instead\n", DevFmtTypeString(oldType), + ERR("Failed to set {}, got {} instead", DevFmtTypeString(oldType), DevFmtTypeString(device->FmtType)); device->Flags.reset(SampleTypeRequest); } - if(device->Frequency != oldFreq && device->Flags.test(FrequencyRequest)) + if(device->mSampleRate != oldFreq && device->Flags.test(FrequencyRequest)) { - WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency); + WARN("Failed to set {}hz, got {}hz instead", oldFreq, device->mSampleRate); device->Flags.reset(FrequencyRequest); } - TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-reset: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + device->mSampleRate, device->mUpdateSize, device->mBufferSize); if(device->Type != DeviceType::Loopback) { @@ -1484,7 +1578,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList 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: %s\n", modeopt->c_str()); + ERR("Unexpected stereo-mode: {}", *modeopt); } } @@ -1513,7 +1607,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList numSends = std::min(numSends, std::clamp(*sendsopt, 0u, uint{MaxSendCount})); device->NumAuxSends = numSends; - TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", + TRACE("Max sources: {} ({} + {}), effect slots: {}, sends: {}", device->SourcesMax, device->NumMonoSources, device->NumStereoSources, device->AuxiliaryEffectSlotMax, device->NumAuxSends); @@ -1529,6 +1623,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList 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; } @@ -1566,10 +1661,10 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList } } if(!(device->DitherDepth > 0.0f)) - TRACE("Dithering disabled\n"); + TRACE("Dithering disabled"); else - TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, - device->DitherDepth); + TRACE("Dithering enabled ({}-bit, {:g})", + float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); if(!optlimit) optlimit = device->configValue({}, "output-limiter"); @@ -1595,7 +1690,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList } } if(!optlimit.value_or(false)) - TRACE("Output limiter disabled\n"); + TRACE("Output limiter disabled"); else { float thrshld{1.0f}; @@ -1622,50 +1717,49 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList sample_delay += limiter->getLookAhead(); device->Limiter = std::move(limiter); - TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB); + 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->Frequency; - TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); + device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->mSampleRate; + TRACE("Fixed device latency: {}ns", device->FixedLatency.count()); FPUCtl mixer_mode{}; - for(ContextBase *ctxbase : *device->mContexts.load()) + auto reset_context = [device](ContextBase *ctxbase) { - auto *context = static_cast(ctxbase); + auto *context = dynamic_cast(ctxbase); + assert(context != nullptr); + if(!context) return; std::unique_lock proplock{context->mPropLock}; std::unique_lock slotlock{context->mEffectSlotLock}; /* Clear out unused effect slot clusters. */ - auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) + auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) -> bool { - const auto cluster = al::span{*clusterptr}; - for(size_t i{0};i < cluster.size();++i) - { - if(cluster[i].InUse) - return false; - } - return true; + return std::none_of(clusterptr->begin(), clusterptr->end(), + std::mem_fn(&EffectSlot::InUse)); }; - auto slotcluster_iter = std::remove_if(context->mEffectSlotClusters.begin(), + auto slotcluster_end = std::remove_if(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), slot_cluster_not_in_use); - context->mEffectSlotClusters.erase(slotcluster_iter, context->mEffectSlotClusters.end()); + context->mEffectSlotClusters.erase(slotcluster_end, context->mEffectSlotClusters.end()); /* Free all wet buffers. Any in use will be reallocated with an updated * configuration in aluInitEffectPanning. */ - for(auto& clusterptr : context->mEffectSlotClusters) + auto clear_wetbuffers = [](ContextBase::EffectSlotCluster &clusterptr) { - const auto cluster = al::span{*clusterptr}; - for(size_t i{0};i < cluster.size();++i) + auto clear_buffer = [](EffectSlot &slot) { - cluster[i].mWetBuffer.clear(); - cluster[i].mWetBuffer.shrink_to_fit(); - cluster[i].Wet.Buffer = {}; - } - } + slot.mWetBuffer.clear(); + slot.mWetBuffer.shrink_to_fit(); + slot.Wet.Buffer = {}; + }; + std::for_each(clusterptr->begin(), clusterptr->end(), clear_buffer); + }; + std::for_each(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), + clear_wetbuffers); if(ALeffectslot *slot{context->mDefaultSlot.get()}) { @@ -1682,8 +1776,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList } if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) - std::fill_n(curarray->end(), curarray->size(), nullptr); - for(auto &sublist : context->mEffectSlotList) + std::fill(curarray->begin()+ptrdiff_t(curarray->size()>>1), curarray->end(), nullptr); + auto reset_slots = [device,context](EffectSlotSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) @@ -1703,15 +1797,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList state->deviceUpdate(device, slot.Buffer); slot.mPropsDirty = true; } - } + }; + std::for_each(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), + reset_slots); + /* Clear all effect slot props to let them get allocated again. */ context->mEffectSlotPropClusters.clear(); context->mFreeEffectSlotProps.store(nullptr, std::memory_order_relaxed); slotlock.unlock(); - const uint num_sends{device->NumAuxSends}; std::unique_lock srclock{context->mSourceLock}; - for(auto &sublist : context->mSourceList) + const uint num_sends{device->NumAuxSends}; + auto reset_sources = [num_sends](SourceSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) @@ -1731,25 +1828,27 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList send.GainLF = 1.0f; send.LFReference = HighPassFreqRef; }; - auto send_begin = source.Send.begin() + static_cast(num_sends); - std::for_each(send_begin, source.Send.end(), clear_send); + const auto sends = al::span{source.Send}.subspan(num_sends); + std::for_each(sends.begin(), sends.end(), clear_send); source.mPropsDirty = true; } - } + }; + std::for_each(context->mSourceList.begin(), context->mSourceList.end(), reset_sources); - for(Voice *voice : context->getVoicesSpan()) + auto reset_voice = [device,num_sends,context](Voice *voice) { /* Clear extraneous property set sends. */ - std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), - VoiceProps::SendData{}); + const auto sendparams = al::span{voice->mProps.Send}.subspan(num_sends); + std::fill(sendparams.begin(), sendparams.end(), VoiceProps::SendData{}); std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); - for(auto &chandata : voice->mChans) + auto clear_wetparams = [num_sends](Voice::ChannelData &chandata) { - std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), - SendParams{}); - } + const auto wetparams = al::span{chandata.mWetParams}.subspan(num_sends); + std::fill(wetparams.begin(), wetparams.end(), SendParams{}); + }; + std::for_each(voice->mChans.begin(), voice->mChans.end(), clear_wetparams); if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) AtomicReplaceHead(context->mFreeVoiceProps, props); @@ -1759,10 +1858,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList 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) - continue; + return; voice->prepare(device); - } + }; + const auto voicespan = context->getVoicesSpan(); + std::for_each(voicespan.begin(), voicespan.end(), reset_voice); + /* Clear all voice props to let them get allocated again. */ context->mVoicePropClusters.clear(); context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); @@ -1772,7 +1874,9 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList UpdateContextProps(context); UpdateAllEffectSlotProps(context); UpdateAllSourceProps(context); - } + }; + auto ctxspan = al::span{*device->mContexts.load()}; + std::for_each(ctxspan.begin(), ctxspan.end(), reset_context); mixer_mode.leave(); device->mDeviceState = DeviceState::Configured; @@ -1784,13 +1888,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList device->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - device->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + device->handleDisconnect("{}", e.what()); return ALC_INVALID_DEVICE; } - TRACE("Post-start: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-start: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + device->mSampleRate, device->mUpdateSize, device->mBufferSize); } return ALC_NO_ERROR; @@ -1800,7 +1904,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList * Updates device parameters as above, and also first clears the disconnected * status, if set. */ -bool ResetDeviceParams(ALCdevice *device, const al::span attrList) +auto ResetDeviceParams(al::Device *device, const al::span attrList) -> bool { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY @@ -1810,8 +1914,9 @@ bool ResetDeviceParams(ALCdevice *device, const al::span attrList) for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { - auto *ctx = static_cast(ctxbase); - if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) + auto *ctx = dynamic_cast(ctxbase); + assert(ctx != nullptr); + if(!ctx || !ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) continue; /* Clear any pending voice changes and reallocate voices to get a @@ -1898,9 +2003,9 @@ ContextRef GetContextRef() noexcept return ContextRef{context}; } -void alcSetError(ALCdevice *device, ALCenum errorCode) +void alcSetError(al::Device *device, ALCenum errorCode) { - WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); + WARN("Error generated on device {}, code {:#04x}", voidp{device}, as_unsigned(errorCode)); if(TrapALCError) { #ifdef _WIN32 @@ -1924,6 +2029,9 @@ void alcSetError(ALCdevice *device, ALCenum errorCode) ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_INVALID_DEVICE; + DeviceRef dev{VerifyDevice(device)}; if(dev) return dev->LastError.exchange(ALC_NO_ERROR); return LastNullDeviceError.exchange(ALC_NO_ERROR); @@ -1939,7 +2047,7 @@ ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept return; } - if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + 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 " @@ -1962,8 +2070,8 @@ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept return; } - if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY - ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + 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 " @@ -1977,112 +2085,101 @@ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept } -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept +ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept -> const ALCchar* { - const ALCchar *value{nullptr}; - switch(param) { - case ALC_NO_ERROR: value = GetNoErrorString(); break; - case ALC_INVALID_ENUM: value = GetInvalidEnumString(); break; - case ALC_INVALID_VALUE: value = GetInvalidValueString(); break; - case ALC_INVALID_DEVICE: value = GetInvalidDeviceString(); break; - case ALC_INVALID_CONTEXT: value = GetInvalidContextString(); break; - case ALC_OUT_OF_MEMORY: value = GetOutOfMemoryString(); break; + 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: - value = GetDefaultName(); - break; + return GetDefaultName(); case ALC_ALL_DEVICES_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type == DeviceType::Capture) - alcSetError(dev.get(), ALC_INVALID_ENUM); - else if(dev->Type == DeviceType::Loopback) - value = GetDefaultName(); - else { - std::lock_guard statelock{dev->StateLock}; - value = dev->DeviceName.c_str(); + alcSetError(dev.get(), ALC_INVALID_ENUM); + return nullptr; } + if(dev->Type == DeviceType::Loopback) + return GetDefaultName(); + + auto statelock = std::lock_guard{dev->StateLock}; + return dev->mDeviceName.c_str(); } - else - { - ProbeAllDevicesList(); - value = alcAllDevicesList.c_str(); - } - break; + ProbeAllDevicesList(); + return alcAllDevicesList.c_str(); case ALC_CAPTURE_DEVICE_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type != DeviceType::Capture) - alcSetError(dev.get(), ALC_INVALID_ENUM); - else { - std::lock_guard statelock{dev->StateLock}; - value = dev->DeviceName.c_str(); + alcSetError(dev.get(), ALC_INVALID_ENUM); + return nullptr; } + + auto statelock = std::lock_guard{dev->StateLock}; + return dev->mDeviceName.c_str(); } - else - { - ProbeCaptureDeviceList(); - value = alcCaptureDeviceList.c_str(); - } - break; + ProbeCaptureDeviceList(); + return alcCaptureDeviceList.c_str(); /* Default devices are always first in the list */ case ALC_DEFAULT_DEVICE_SPECIFIER: - value = GetDefaultName(); - break; + return GetDefaultName(); case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: if(alcAllDevicesList.empty()) ProbeAllDevicesList(); /* Copy first entry as default. */ - alcDefaultAllDevicesSpecifier = alcAllDevicesList.substr(0, alcAllDevicesList.find('\0')); - value = alcDefaultAllDevicesSpecifier.c_str(); - break; + 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. */ - alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.substr(0, - alcCaptureDeviceList.find('\0')); - value = alcCaptureDefaultDeviceSpecifier.c_str(); - break; + if(!alcCaptureDeviceArray.empty()) + alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); + else + alcCaptureDefaultDeviceSpecifier.clear(); + return alcCaptureDefaultDeviceSpecifier.c_str(); case ALC_EXTENSIONS: if(VerifyDevice(Device)) - value = GetExtensionList().data(); - else - value = GetNoDeviceExtList().data(); - break; + return GetExtensionList(); + return GetNoDeviceExtList(); case ALC_HRTF_SPECIFIER_SOFT: if(DeviceRef dev{VerifyDevice(Device)}) { std::lock_guard statelock{dev->StateLock}; - value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); + return dev->mHrtf ? dev->mHrtfName.c_str() : ""; } - else - alcSetError(nullptr, ALC_INVALID_DEVICE); - break; + alcSetError(nullptr, ALC_INVALID_DEVICE); + return nullptr; default: alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM); - break; } - return value; + return nullptr; } - -static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) +namespace { +auto GetIntegerv(al::Device *device, ALCenum param, const al::span values) -> size_t { if(values.empty()) { @@ -2184,20 +2281,20 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } /* render device */ - auto NumAttrsForDevice = [](const ALCdevice *aldev) noexcept -> uint8_t + auto NumAttrsForDevice = [device]() noexcept -> uint8_t { - if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) + if(device->Type == DeviceType::Loopback && device->FmtChans == DevFmtAmbi3D) return 37; return 31; }; switch(param) { case ALC_ATTRIBUTES_SIZE: - values[0] = NumAttrsForDevice(device); + values[0] = NumAttrsForDevice(); return 1; case ALC_ALL_ATTRIBUTES: - if(values.size() >= NumAttrsForDevice(device)) + if(values.size() >= NumAttrsForDevice()) { size_t i{0}; values[i++] = ALC_MAJOR_VERSION; @@ -2210,11 +2307,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = alcEFXMinorVersion; values[i++] = ALC_FREQUENCY; - values[i++] = static_cast(device->Frequency); + values[i++] = static_cast(device->mSampleRate); if(device->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; - values[i++] = static_cast(device->Frequency / device->UpdateSize); + values[i++] = static_cast(device->mSampleRate / device->mUpdateSize); values[i++] = ALC_SYNC; values[i++] = ALC_FALSE; @@ -2265,7 +2362,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = static_cast(device->getOutputMode1()); values[i++] = 0; - assert(i == NumAttrsForDevice(device)); + assert(i == NumAttrsForDevice()); return i; } alcSetError(device, ALC_INVALID_VALUE); @@ -2288,7 +2385,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_FREQUENCY: - values[0] = static_cast(device->Frequency); + values[0] = static_cast(device->mSampleRate); return 1; case ALC_REFRESH: @@ -2297,7 +2394,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span alcSetError(device, ALC_INVALID_DEVICE); return 0; } - values[0] = static_cast(device->Frequency / device->UpdateSize); + values[0] = static_cast(device->mSampleRate / device->mUpdateSize); return 1; case ALC_SYNC: @@ -2401,6 +2498,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } return 0; } +} // namespace ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { @@ -2428,7 +2526,7 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, return; } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept -> size_t + auto NumAttrsForDevice = [](al::Device *aldev) noexcept -> size_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 41; @@ -2448,12 +2546,12 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, { size_t i{0}; valuespan[i++] = ALC_FREQUENCY; - valuespan[i++] = dev->Frequency; + valuespan[i++] = dev->mSampleRate; if(dev->Type != DeviceType::Loopback) { valuespan[i++] = ALC_REFRESH; - valuespan[i++] = dev->Frequency / dev->UpdateSize; + valuespan[i++] = dev->mSampleRate / dev->mUpdateSize; valuespan[i++] = ALC_SYNC; valuespan[i++] = ALC_FALSE; @@ -2505,7 +2603,7 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, valuespan[i++] = clock.Latency.count(); valuespan[i++] = ALC_OUTPUT_MODE_SOFT; - valuespan[i++] = al::to_underlying(device->getOutputMode1()); + valuespan[i++] = al::to_underlying(dev->getOutputMode1()); valuespan[i++] = 0; } @@ -2514,15 +2612,18 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, case ALC_DEVICE_CLOCK_SOFT: { uint samplecount, refcount; - nanoseconds basecount; + seconds clocksec; + nanoseconds clocknsec; do { refcount = dev->waitForMix(); - basecount = dev->mClockBase.load(std::memory_order_relaxed); 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)); - basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - valuespan[0] = basecount.count(); + + valuespan[0] = nanoseconds{clocksec + nanoseconds{clocknsec} + + nanoseconds{seconds{samplecount}}/dev->mSampleRate}.count(); } break; @@ -2560,7 +2661,8 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A } const std::string_view tofind{extName}; - const auto extlist = dev ? GetExtensionList() : GetNoDeviceExtList(); + const auto extlist = dev ? std::string_view{GetExtensionList()} + : std::string_view{GetNoDeviceExtList()}; auto matchpos = extlist.find(tofind); while(matchpos != std::string_view::npos) { @@ -2586,7 +2688,7 @@ ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar return nullptr; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &func : eaxFunctions) @@ -2614,7 +2716,7 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e return 0; } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &enm : eaxEnumerations) @@ -2683,14 +2785,14 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin { const float valf{*volopt}; if(!std::isfinite(valf)) - ERR("volume-adjust must be finite: %f\n", valf); + ERR("volume-adjust must be finite: {:f}", valf); else { const float db{std::clamp(valf, -24.0f, 24.0f)}; if(db != valf) - WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); + WARN("volume-adjust clamped: {:f}, range: +/-24", valf); context->mGainBoost = std::pow(10.0f, db/20.0f); - TRACE("volume-adjust gain: %f\n", context->mGainBoost); + TRACE("volume-adjust gain: {:f}", context->mGainBoost); } } @@ -2700,7 +2802,7 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin /* Allocate a new context array, which holds 1 more than the current/ * old array. */ - auto *oldarray = device->mContexts.load(); + auto *oldarray = dev->mContexts.load(); auto newarray = ContextArray::Create(oldarray->size() + 1); /* Copy the current/old context handles to the new array, appending the @@ -2731,15 +2833,18 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); else - ERR("Failed to initialize the default effect\n"); + ERR("Failed to initialize the default effect"); } - TRACE("Created context %p\n", voidp{context.get()}); + TRACE("Created context {}", voidp{context.get()}); return context.release(); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { + if(!gProcessRunning) + return; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter == ContextList.end() || *iter != context) @@ -2755,8 +2860,7 @@ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept ContextRef ctx{*iter}; ContextList.erase(iter); - ALCdevice *Device{ctx->mALDevice.get()}; - + auto *Device = ctx->mALDevice.get(); std::lock_guard statelock{Device->StateLock}; ctx->deinit(); } @@ -2855,7 +2959,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { - TRACE("Opening playback device \"%.*s\"\n", al::sizei(devname), devname.data()); + 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 @@ -2873,21 +2977,28 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep || al::starts_with(devname, "'("sv) || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } else - TRACE("Opening default playback device\n"); + TRACE("Opening default playback device"); const uint DefaultSends{ -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Playback}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Playback}}; if(!device) { - WARN("Failed to create playback device handle\n"); + WARN("Failed to create playback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -2895,9 +3006,9 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep /* Set output format */ device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - device->Frequency = DefaultOutputRate; - device->UpdateSize = DefaultUpdateSize; - device->BufferSize = DefaultUpdateSize * DefaultNumUpdates; + device->mSampleRate = DefaultOutputRate; + device->mUpdateSize = DefaultUpdateSize; + device->mBufferSize = DefaultUpdateSize * DefaultNumUpdates; device->SourcesMax = 256; device->NumStereoSources = 1; @@ -2909,27 +3020,52 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcep auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); std::lock_guard listlock{ListLock}; backend->open(devname); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open playback device: %s\n", e.what()); + WARN("Failed to open playback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } + auto checkopt = [&device](const char *envname, const std::string_view optname) + { + if(auto optval = al::getenv(envname)) return optval; + return device->configValue("game_compat", optname); + }; + if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) + { + device->mVendorOverride = std::move(*overrideopt); + TRACE("Overriding vendor string: \"{}\"", device->mVendorOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) + { + device->mVersionOverride = std::move(*overrideopt); + TRACE("Overriding version string: \"{}\"", device->mVersionOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) + { + device->mRendererOverride = std::move(*overrideopt); + TRACE("Overriding renderer string: \"{}\"", device->mRendererOverride); + } + { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } - TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + TRACE("Created device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_FALSE; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) @@ -2964,7 +3100,7 @@ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept for(ContextRef &context : orphanctxs) { - WARN("Releasing orphaned context %p\n", voidp{context.get()}); + WARN("Releasing orphaned context {}", voidp{context.get()}); context->deinit(); } orphanctxs.clear(); @@ -3001,18 +3137,25 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { - TRACE("Opening capture device \"%.*s\"\n", al::sizei(devname), devname.data()); + TRACE("Opening capture device \"{}\"", devname); if(al::case_compare(devname, GetDefaultName()) == 0 || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } else - TRACE("Opening default capture device\n"); + TRACE("Opening default capture device"); - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Capture}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Capture}}; if(!device) { - WARN("Failed to create capture device handle\n"); + WARN("Failed to create capture device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -3024,28 +3167,29 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, return nullptr; } - device->Frequency = frequency; + device->mSampleRate = frequency; device->FmtChans = decompfmt->chans; device->FmtType = decompfmt->type; device->Flags.set(FrequencyRequest); device->Flags.set(ChannelsRequest); device->Flags.set(SampleTypeRequest); - device->UpdateSize = static_cast(samples); - device->BufferSize = static_cast(samples); + device->mUpdateSize = static_cast(samples); + device->mBufferSize = static_cast(samples); - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), - DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, - device->BufferSize); + TRACE("Capture format: {}, {}, {}hz, {} / {} buffer", + DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), + device->mSampleRate, device->mUpdateSize, device->mBufferSize); try { auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); std::lock_guard listlock{ListLock}; backend->open(devname); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open capture device: %s\n", e.what()); + WARN("Failed to open capture device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; @@ -3058,12 +3202,15 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, } device->mDeviceState = DeviceState::Configured; - TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); + TRACE("Created capture device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { + if(!gProcessRunning) + return ALC_FALSE; + std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) @@ -3112,8 +3259,8 @@ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - dev->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); } } @@ -3183,16 +3330,16 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN } const uint DefaultSends{ -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; - DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Loopback}}; + auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Loopback}}; if(!device) { - WARN("Failed to create loopback device handle\n"); + WARN("Failed to create loopback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } @@ -3202,10 +3349,10 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN device->NumAuxSends = DefaultSends; //Set output format - device->BufferSize = 0; - device->UpdateSize = 0; + device->mBufferSize = 0; + device->mUpdateSize = 0; - device->Frequency = DefaultOutputRate; + device->mSampleRate = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; @@ -3216,10 +3363,11 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN auto backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), BackendType::Playback); backend->open("Loopback"); + device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { - WARN("Failed to open loopback device: %s\n", e.what()); + WARN("Failed to open loopback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; @@ -3231,7 +3379,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN DeviceList.emplace(iter, device.get()); } - TRACE("Created loopback device %p\n", voidp{device.get()}); + TRACE("Created loopback device {}", voidp{device.get()}); return device.release(); } @@ -3267,12 +3415,13 @@ ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device #endif ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(!device || device->Type != DeviceType::Loopback) UNLIKELY - alcSetError(device, ALC_INVALID_DEVICE); + auto aldev = dynamic_cast(device); + if(!aldev || aldev->Type != DeviceType::Loopback) UNLIKELY + alcSetError(aldev, ALC_INVALID_DEVICE); else if(samples < 0 || (samples > 0 && buffer == nullptr)) UNLIKELY - alcSetError(device, ALC_INVALID_VALUE); + alcSetError(aldev, ALC_INVALID_VALUE); else - device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); + aldev->renderSamples(buffer, static_cast(samples), aldev->channelsFromFmt()); } @@ -3313,13 +3462,13 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept return; if(dev->mDeviceState < DeviceState::Configured) { - WARN("Cannot resume unconfigured device\n"); + WARN("Cannot resume unconfigured device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } if(!dev->Connected.load()) { - WARN("Cannot resume a disconnected device\n"); + WARN("Cannot resume a disconnected device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } @@ -3333,14 +3482,14 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { - ERR("%s\n", e.what()); - dev->handleDisconnect("%s", e.what()); + ERR("{}", e.what()); + dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } - TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", + TRACE("Post-resume: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), - dev->Frequency, dev->UpdateSize, dev->BufferSize); + dev->mSampleRate, dev->mUpdateSize, dev->mBufferSize); } @@ -3418,15 +3567,22 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { - if(devname.length() >= std::numeric_limits::max()) + if(devname.length() >= size_t{std::numeric_limits::max()}) { - ERR("Device name too long (%zu >= %d)\n", devname.length(), + ERR("Device name too long ({} >= {})", devname.length(), std::numeric_limits::max()); alcSetError(dev.get(), ALC_INVALID_VALUE); return ALC_FALSE; } if(al::case_compare(devname, GetDefaultName()) == 0) devname = {}; + else + { + const auto prefix = GetDevicePrefix(); + if(!prefix.empty() && devname.size() > prefix.size() + && al::starts_with(devname, prefix)) + devname = devname.substr(prefix.size()); + } } /* Force the backend device to stop first since we're opening another one. */ @@ -3446,7 +3602,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, listlock.unlock(); newbackend = nullptr; - WARN("Failed to reopen playback device: %s\n", e.what()); + WARN("Failed to reopen playback device: {}", e.what()); alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); @@ -3458,16 +3614,41 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception &be) { - ERR("%s\n", be.what()); - dev->handleDisconnect("%s", be.what()); + 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 %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); + TRACE("Reopened device {}, \"{}\"", voidp{dev.get()}, dev->mDeviceName); + + std::string{}.swap(dev->mVendorOverride); + std::string{}.swap(dev->mVersionOverride); + std::string{}.swap(dev->mRendererOverride); + auto checkopt = [&dev](const char *envname, const std::string_view optname) + { + if(auto optval = al::getenv(envname)) return optval; + return dev->configValue("game_compat", optname); + }; + if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) + { + dev->mVendorOverride = std::move(*overrideopt); + TRACE("Overriding vendor string: \"{}\"", dev->mVendorOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) + { + dev->mVersionOverride = std::move(*overrideopt); + TRACE("Overriding version string: \"{}\"", dev->mVersionOverride); + } + if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) + { + dev->mRendererOverride = std::move(*overrideopt); + TRACE("Overriding renderer string: \"{}\"", dev->mRendererOverride); + } /* Always return true even if resetting fails. It shouldn't fail, but this * is primarily to avoid confusion by the app seeing the function return @@ -3493,27 +3674,25 @@ FORCE_ALIGN ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCe auto etype = alc::GetEventType(eventType); if(!etype) { - WARN("Invalid event type: 0x%04x\n", eventType); + WARN("Invalid event type: {:#04x}", as_unsigned(eventType)); alcSetError(nullptr, ALC_INVALID_ENUM); - return ALC_EVENT_NOT_SUPPORTED_SOFT; + return ALC_FALSE; } auto supported = alc::EventSupport::NoSupport; switch(deviceType) { - case ALC_PLAYBACK_DEVICE_SOFT: - if(PlaybackFactory) - supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); - break; + 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); - break; - - default: - WARN("Invalid device type: 0x%04x\n", deviceType); - alcSetError(nullptr, ALC_INVALID_ENUM); + case ALC_CAPTURE_DEVICE_SOFT: + if(CaptureFactory) + supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); + return al::to_underlying(supported); } - return al::to_underlying(supported); + WARN("Invalid device type: {:#04x}", as_unsigned(deviceType)); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; } diff --git a/libs/openal-soft/alc/alconfig.cpp b/libs/openal-soft/alc/alconfig.cpp index 6f69e5b6..e0a3920c 100644 --- a/libs/openal-soft/alc/alconfig.cpp +++ b/libs/openal-soft/alc/alconfig.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -47,9 +46,10 @@ #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" +#include "filesystem.h" #include "strutils.h" -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP #include // !!This is important!! #include #include @@ -61,7 +61,7 @@ namespace { using namespace std::string_view_literals; -#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP) +#if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; @@ -153,7 +153,7 @@ void LoadConfigFromFile(std::istream &f) auto endpos = buffer.find(']', 1); if(endpos == 1 || endpos == std::string::npos) { - ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: bad line \"{}\"", buffer); continue; } if(buffer[endpos+1] != '\0') @@ -164,7 +164,7 @@ void LoadConfigFromFile(std::istream &f) if(last < buffer.size() && buffer[last] != '#') { - ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: bad line \"{}\"", buffer); continue; } } @@ -234,7 +234,7 @@ void LoadConfigFromFile(std::istream &f) auto sep = buffer.find('='); if(sep == std::string::npos) { - ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto keypart = std::string_view{buffer}.substr(0, sep++); @@ -242,7 +242,7 @@ void LoadConfigFromFile(std::istream &f) keypart.remove_suffix(1); if(keypart.empty()) { - ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); + ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto valpart = std::string_view{buffer}.substr(sep); @@ -257,9 +257,9 @@ void LoadConfigFromFile(std::istream &f) } fullKey += keypart; - if(valpart.size() > std::numeric_limits::max()) + if(valpart.size() > size_t{std::numeric_limits::max()}) { - ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str()); + ERR(" config parse error: value too long in line \"{}\"", buffer); continue; } if(valpart.size() > 1) @@ -272,7 +272,7 @@ void LoadConfigFromFile(std::istream &f) } } - TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data()); + TRACE(" setting '{}' = '{}'", fullKey, valpart); /* Check if we already have this option set */ auto find_key = [&fullKey](const ConfigEntry &entry) -> bool @@ -291,11 +291,12 @@ void LoadConfigFromFile(std::istream &f) ConfOpts.shrink_to_fit(); } -const char *GetConfigValue(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName) +auto GetConfigValue(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> const std::string& { + static const auto emptyString = std::string{}; if(keyName.empty()) - return nullptr; + return emptyString; std::string key; if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) @@ -314,14 +315,14 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie [&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); if(iter != ConfOpts.cend()) { - TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + TRACE("Found option {} = \"{}\"", key, iter->value); if(!iter->value.empty()) - return iter->value.c_str(); - return nullptr; + return iter->value; + return emptyString; } if(devName.empty()) - return nullptr; + return emptyString; return GetConfigValue({}, blockName, keyName); } @@ -331,12 +332,11 @@ const char *GetConfigValue(const std::string_view devName, const std::string_vie #ifdef _WIN32 void ReadALConfig() { - namespace fs = std::filesystem; fs::path path; #if !defined(_GAMING_XBOX) { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP std::unique_ptr bufstore; const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(bufstore))}; @@ -352,8 +352,8 @@ void ReadALConfig() path = fs::path{buffer}; path /= L"alsoft.ini"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -362,17 +362,17 @@ void ReadALConfig() path = fs::u8path(GetProcBinary().path); if(!path.empty()) { - path /= "alsoft.ini"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + path /= L"alsoft.ini"; + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confpath = al::getenv(L"ALSOFT_CONF")) { path = *confpath; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -381,11 +381,10 @@ void ReadALConfig() void ReadALConfig() { - namespace fs = std::filesystem; fs::path path{"/etc/openal/alsoft.conf"}; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; @@ -409,13 +408,13 @@ void ReadALConfig() } if(!path.is_absolute()) - WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str()); + WARN("Ignoring XDG config dir: {}", al::u8_as_char(path.u8string())); else { path /= "alsoft.conf"; - TRACE("Loading config %s...\n", path.u8string().c_str()); - if(std::ifstream f{path}; f.is_open()) + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -441,7 +440,7 @@ void ReadALConfig() path = *homedir; path /= ".alsoftrc"; - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } @@ -462,7 +461,7 @@ void ReadALConfig() } if(!path.empty()) { - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } @@ -472,66 +471,99 @@ void ReadALConfig() { path /= "alsoft.conf"; - TRACE("Loading config %s...\n", path.u8string().c_str()); + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { - TRACE("Loading config %s...\n", confname->c_str()); + TRACE("Loading config {}...", *confname); if(std::ifstream f{*confname}; f.is_open()) LoadConfigFromFile(f); } } #endif -std::optional ConfigValueStr(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueStr(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) return val; return std::nullopt; } -std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName) +auto ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return static_cast(std::strtol(val, nullptr, 0)); + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return static_cast(std::stol(val, nullptr, 0)); + } + catch(std::exception&) { + WARN("Option is not an int: {} = {}", keyName, val); + } + return std::nullopt; } -std::optional ConfigValueUInt(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueUInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return static_cast(std::strtoul(val, nullptr, 0)); + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return static_cast(std::stoul(val, nullptr, 0)); + } + catch(std::exception&) { + WARN("Option is not an unsigned int: {} = {}", keyName, val); + } return std::nullopt; } -std::optional ConfigValueFloat(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueFloat(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return std::strtof(val, nullptr); + 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; } -std::optional ConfigValueBool(const std::string_view devName, - const std::string_view blockName, const std::string_view keyName) +auto ConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + 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; } -bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, - const std::string_view keyName, bool def) +auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) -> bool { - if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + 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; } diff --git a/libs/openal-soft/alc/alu.cpp b/libs/openal-soft/alc/alu.cpp index e3b65f47..aab7eeec 100644 --- a/libs/openal-soft/alc/alu.cpp +++ b/libs/openal-soft/alc/alu.cpp @@ -19,6 +19,7 @@ */ #include "config.h" +#include "config_simd.h" #include "alu.h" @@ -26,23 +27,25 @@ #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 "alnumbers.h" #include "alnumeric.h" +#include "alsem.h" #include "alspan.h" #include "alstring.h" #include "atomic.h" @@ -70,6 +73,7 @@ #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" @@ -78,19 +82,18 @@ #include "ringbuffer.h" #include "strutils.h" #include "vecmat.h" -#include "vector.h" struct CTag; -#ifdef HAVE_SSE +#if HAVE_SSE struct SSETag; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 struct SSE2Tag; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 struct SSE4Tag; #endif -#ifdef HAVE_NEON +#if HAVE_NEON struct NEONTag; #endif struct PointTag; @@ -135,19 +138,19 @@ float NfcScale{1.0f}; using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; inline HrtfDirectMixerFunc SelectHrtfMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixDirectHrtf_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixDirectHrtf_; #endif @@ -176,7 +179,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab state->sf = sf; state->m = table->m[si]; state->l = (state->m/2) - 1; - state->filter = table->Tab + table->filterOffset[si]; + state->filter = table->Tab.subspan(table->filterOffset[si]); } inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) @@ -186,59 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) case Resampler::Point: return Resample_; case Resampler::Linear: -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif return Resample_; - case Resampler::Cubic: -#ifdef HAVE_NEON + case Resampler::Spline: + case Resampler::Gaussian: +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE4_1 +#if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif -#ifdef HAVE_SSE2 +#if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; case Resampler::BSinc12: case Resampler::BSinc24: + case Resampler::BSinc48: if(increment > MixerFracOne) { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; } - /* fall-through */ + [[fallthrough]]; case Resampler::FastBSinc12: case Resampler::FastBSinc24: -#ifdef HAVE_NEON + case Resampler::FastBSinc48: +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif @@ -268,7 +274,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState case Resampler::Point: case Resampler::Linear: break; - case Resampler::Cubic: + case Resampler::Spline: + state->emplace(al::span{gSplineFilter.mTable}); + break; + case Resampler::Gaussian: state->emplace(al::span{gGaussianFilter.mTable}); break; case Resampler::FastBSinc12: @@ -279,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState 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); } @@ -290,8 +303,8 @@ void DeviceBase::ProcessHrtf(const size_t SamplesToDo) const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; - MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData.data(), - mHrtfState->mTemp, mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); + MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, + mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo); } void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) @@ -330,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo) const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ - Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); + Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo), + al::span{RealOut.Buffer[ridx]}.first(SamplesToDo)); } @@ -430,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx) ctx->mParams.Velocity = rot * vel; ctx->mParams.Gain = props->Gain * ctx->mGainBoost; - ctx->mParams.MetersPerUnit = props->MetersPerUnit; + 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; + ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity +#if ALSOFT_EAX + / props->DistanceFactor +#endif + ; ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; ctx->mParams.mDistanceModel = props->mDistanceModel; @@ -458,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; + + slot->RoomRolloff = 0.0f; + slot->DecayTime = 0.0f; + slot->DecayLFRatio = 0.0f; + slot->DecayHFRatio = 0.0f; + slot->DecayHFLimit = false; + slot->AirAbsorptionGainHF = 1.0f; if(auto *reverbprops = std::get_if(&props->Props)) { slot->RoomRolloff = reverbprops->RoomRolloffFactor; - slot->DecayTime = reverbprops->DecayTime; - slot->DecayLFRatio = reverbprops->DecayLFRatio; - slot->DecayHFRatio = reverbprops->DecayHFRatio; - slot->DecayHFLimit = reverbprops->DecayHFLimit; slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; - } - else - { - slot->RoomRolloff = 0.0f; - slot->DecayTime = 0.0f; - slot->DecayLFRatio = 0.0f; - slot->DecayHFRatio = 0.0f; - slot->DecayHFLimit = false; - slot->AirAbsorptionGainHF = 1.0f; + /* If this effect slot's Auxiliary Send Auto is off, don't apply the + * automatic send adjustments based on source distance. + */ + if(slot->AuxSendAuto) + { + slot->DecayTime = reverbprops->DecayTime; + slot->DecayLFRatio = reverbprops->DecayLFRatio; + slot->DecayHFRatio = reverbprops->DecayHFRatio; + slot->DecayHFLimit = reverbprops->DecayHFLimit; + } } EffectState *state{props->State.release()}; @@ -489,9 +515,9 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa /* Otherwise, if it would be deleted send it off with a release event. */ RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len > 0) LIKELY + if(evt_vec[0].len > 0) LIKELY { - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mEffectState = oldstate; ring->writeAdvance(1); } @@ -508,14 +534,13 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa AtomicReplaceHead(context->mFreeEffectSlotProps, props); - EffectTarget output; - if(EffectSlot *target{slot->Target}) - output = EffectTarget{&target->Wet, nullptr}; - else + const auto output = [slot,context]() -> EffectTarget { + if(EffectSlot *target{slot->Target}) + return EffectTarget{&target->Wet, nullptr}; DeviceBase *device{context->mDevice}; - output = EffectTarget{&device->Dry, &device->RealOut}; - } + return EffectTarget{&device->Dry, &device->RealOut}; + }(); state->update(context, slot, &slot->mEffectProps, output); return true; } @@ -683,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) /* Don't do anything for < 2nd order. */ if(order < 2) return; - auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, - const AmbiRotateMatrix &R) + static constexpr auto P = [](const int i, const int l, const int a, const int n, + const size_t last_band, const AmbiRotateMatrix &R) { const float ri1{ R[ 1+2][static_cast(i+2_z)]}; const float rim1{R[-1+2][static_cast(i+2_z)]}; @@ -698,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) return ri0*R[last_band + static_cast(l-1_z+n)][y]; }; - auto U = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { return P(0, l, m, n, last_band, R); }; - auto V = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { using namespace al::numbers; @@ -719,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) const float p1{P(-1, l, -m-1, n, last_band, R)}; return d ? p1*sqrt2_v : (p0 + p1); }; - auto W = [P](const int l, const int m, const int n, const size_t last_band, + static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { assert(m != 0); @@ -833,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; - const auto Frequency = static_cast(Device->Frequency); + const auto Frequency = static_cast(Device->mSampleRate); const uint NumSends{Device->NumAuxSends}; const size_t num_channels{voice->mChans.size()}; @@ -915,6 +940,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con 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; @@ -1176,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con } else for(size_t c{0};c < num_channels;c++) { - using namespace al::numbers; - /* Skip LFE */ if(chans[c].channel == LFE) continue; const float pangain{SelectChannelGain(chans[c].channel)}; @@ -1187,7 +1214,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * the source position, at full spread (pi*2), each channel is * left unchanged. */ - const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + const float a{1.0f - (al::numbers::inv_pi_v/2.0f)*Spread}; std::array pos{ lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[1], ypos, a), @@ -1303,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mChans[0].mWetParams[i].Gains.Target); } } - else + else for(size_t c{0};c < num_channels;c++) { - using namespace al::numbers; + const auto pangain = SelectChannelGain(chans[c].channel); - for(size_t c{0};c < num_channels;c++) + /* Special-case LFE */ + if(chans[c].channel == LFE) { - const float pangain{SelectChannelGain(chans[c].channel)}; - - /* Special-case LFE */ - if(chans[c].channel == LFE) + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) - { - const uint idx{Device->channelIdxByName(chans[c].channel)}; - if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base - * pangain; - } - continue; + const auto idx = uint{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } + continue; + } - /* Warp the channel position toward the source position as - * the spread decreases. With no spread, all channels are - * at the source position, at full spread (pi*2), each - * channel position is left unchanged. - */ - const float a{1.0f - (inv_pi_v/2.0f)*Spread}; - std::array pos{ - lerpf(chans[c].pos[0], xpos, a), - lerpf(chans[c].pos[1], ypos, a), - lerpf(chans[c].pos[2], zpos, a)}; - const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; - if(len < 1.0f) - { - pos[0] /= len; - pos[1] /= len; - pos[2] /= len; - } + /* Warp the channel position toward the source position as the + * spread decreases. With no spread, all channels are at the + * source position, at full spread (pi*2), each channel + * position is left unchanged. + */ + const auto a = 1.0f - (al::numbers::inv_pi_v/2.0f)*Spread; + auto pos = std::array{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]); + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } - if(Device->mRenderMode == RenderMode::Pairwise) - pos = ScaleAzimuthFront3(pos); - const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); + if(Device->mRenderMode == RenderMode::Pairwise) + pos = ScaleAzimuthFront3(pos); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); - ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, - voice->mChans[c].mDryParams.Gains.Target); - for(uint i{0};i < NumSends;i++) - { - if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, - voice->mChans[c].mWetParams[i].Gains.Target); - } + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, + voice->mChans[c].mDryParams.Gains.Target); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, + voice->mChans[c].mWetParams[i].Gains.Target); } } } @@ -1449,7 +1470,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; - std::array SendSlots; + std::array SendSlots{}; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) @@ -1466,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex /* Calculate the stepping value */ const auto Pitch = static_cast(voice->mFrequency) / - static_cast(Device->Frequency) * props->Pitch; + static_cast(Device->mSampleRate) * props->Pitch; if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ - GainTriplet DryGain; - DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * + GainTriplet DryGain{}; + DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * context->mParams.Gain, GainMixMax); DryGain.HF = props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - std::array WetGain; + std::array WetGain{}; for(uint i{0};i < Device->NumAuxSends;i++) { WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * @@ -1502,33 +1523,24 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa voice->mDirect.Buffer = Device->Dry.Buffer; std::array SendSlots{}; std::array RoomRolloff{}; - std::bitset UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) + { SendSlots[i] = nullptr; - else if(SendSlots[i]->AuxSendAuto) + 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. - * - * Generic Software also applies these rolloff factors regardless - * of any setting. It doesn't seem to use the effect slot's send - * auto for anything, though as far as I understand, it's supposed - * to control whether the send gets the same gain/gainhf as the - * direct path (excluding the filter). */ RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff; - } - else - UseDryAttnForRoom.set(i); - if(!SendSlots[i]) - voice->mSend[i].Buffer = {}; - else voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; + } } /* Transform source to listener space (convert to head relative) */ @@ -1663,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa std::array WetGain{}; for(uint i{0};i < NumSends;i++) { - WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * + const auto gain = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * context->mParams.Gain; - /* If this effect slot's Auxiliary Send Auto is off, then use the dry - * path distance and cone attenuation, otherwise use the wet (room) - * path distance and cone attenuation. The send filter is used instead - * of the direct filter, regardless. - */ - const bool use_room{!UseDryAttnForRoom.test(i)}; - const float gain{use_room ? WetGainBase[i] : DryGainBase}; WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); - WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; + WetGain[i].HF = WetConeHF * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } /* Distance-based air absorption and initial send decay. */ if(Distance > props->RefDistance) LIKELY { - const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; - const float distance_meters{distance_base * context->mParams.MetersPerUnit}; - const float dryabsorb{distance_meters * props->AirAbsorptionFactor}; - if(dryabsorb > std::numeric_limits::epsilon()) - DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb); + /* FIXME: In keeping with EAX, the base air absorption gain should be + * taken from the reverb property in the "primary fx slot" when it has + * a reverb effect and the environment flag set, and be applied to the + * direct path and all environment sends, rather than each path using + * the air absorption gain associated with the given slot's effect. At + * this point in the mixer, and even in EFX itself, there's no concept + * of a "primary fx slot" so it's unclear which effect slot should be + * checked. + * + * The HF reference is also intended to be handled the same way, but + * again, there's no concept of a "primary fx slot" here and no way to + * know which effect slot to look at for the reference frequency. + */ + const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor}; + const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit}; + const auto absorb = float{distance_meters * props->AirAbsorptionFactor}; + if(absorb > std::numeric_limits::epsilon()) + DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb); /* If the source's Auxiliary Send Filter Gain Auto is off, no extra * adjustment is applied to the send gains. @@ -1694,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; - if(distance_meters > std::numeric_limits::epsilon()) - WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); - - /* If this effect slot's Auxiliary Send Auto is off, don't apply - * the automatic initial reverb decay. - * - * NOTE: Generic Software applies the initial decay regardless of - * this setting. It doesn't seem to use it for anything, only the - * source's send filter gain auto flag affects this. - */ - if(!SendSlots[i]->AuxSendAuto) - continue; + if(SendSlots[i]->AirAbsorptionGainHF < 1.0f + && absorb > std::numeric_limits::epsilon()) + WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb); const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; @@ -1719,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa * with the reverb and source rolloff parameters. */ const float baseAttn{DryAttnBase}; - const float fact{distance_base / DecayDistance}; + const float fact{distance_meters / DecayDistance}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; } @@ -1764,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Adjust pitch based on the buffer and output frequencies, and calculate * fixed-point stepping value. */ - Pitch *= static_cast(voice->mFrequency) / static_cast(Device->Frequency); + Pitch *= static_cast(voice->mFrequency) / static_cast(Device->mSampleRate); if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; + if(evt_vec[0].len < 1) return; - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; switch(state) { @@ -1929,7 +1938,7 @@ void ProcessVoiceChanges(ContextBase *ctx) } void ProcessParamUpdates(ContextBase *ctx, const al::span slots, - const al::span voices) + const al::span sorted_slots, const al::span voices) { ProcessVoiceChanges(ctx); @@ -1937,9 +1946,9 @@ void ProcessParamUpdates(ContextBase *ctx, const al::span slots, if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; - auto sorted_slots = al::to_address(slots.end()); + auto sorted_slot_base = al::to_address(sorted_slots.begin()); for(EffectSlot *slot : slots) - force |= CalcEffectSlotParams(slot, sorted_slots, ctx); + force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx); for(Voice *voice : voices) { @@ -1955,97 +1964,93 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + - nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/ - device->Frequency}; + const auto curtime = device->getClockTime(); - for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) + auto proc_context = [SamplesToDo,curtime](ContextBase *ctx) { - auto auxslots = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; - const al::span voices{ctx->getVoicesSpanAcquired()}; + const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; + const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); + const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); + const auto voices = ctx->getVoicesSpanAcquired(); /* Process pending property updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots, voices); + ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ - for(EffectSlot *slot : auxslots) + auto clear_wetbuffers = [](EffectSlot *slot) { - for(auto &buffer : slot->Wet.Buffer) - buffer.fill(0.0f); - } + auto clear_buffer = [](const FloatBufferSpan buffer) + { std::fill(buffer.begin(), buffer.end(), 0.0f); }; + std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer); + }; + std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers); /* Process voices that have a playing source. */ - for(Voice *voice : voices) + auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice) { const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; if(vstate != Voice::Stopped && vstate != Voice::Pending) voice->mix(vstate, ctx, curtime, SamplesToDo); - } + }; + std::for_each(voices.begin(), voices.end(), proc_voice); /* Process effects. */ if(!auxslots.empty()) { /* Sort the slots into extra storage, so that effect slots come - * before their effect slot target (or their targets' target). + * before their effect slot target (or their targets' target). Skip + * sorting if it has already been done. */ - const al::span sorted_slots{al::to_address(auxslots.end()), auxslots.size()}; - /* Skip sorting if it has already been done. */ if(!sorted_slots[0]) { - /* First, copy the slots to the sorted list, then partition the - * sorted list so that all slots without a target slot go to - * the end. + /* First, copy the slots to the sorted list and partition them, + * so that all slots without a target slot go to the end. */ - std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); - auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), - [](const EffectSlot *slot) noexcept -> bool - { return slot->Target != nullptr; }); + auto has_target = [](const EffectSlot *slot) noexcept -> bool + { return slot->Target != nullptr; }; + auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(), + sorted_slots.begin(), sorted_slots.rbegin(), has_target).first; /* There must be at least one slot without a slot target. */ assert(split_point != sorted_slots.end()); - /* Simple case: no more than 1 slot has a target slot. Either - * all slots go right to the output, or the remaining one must - * target an already-partitioned slot. + /* 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. */ - if(split_point - sorted_slots.begin() > 1) + auto next_target = sorted_slots.end(); + while(std::distance(sorted_slots.begin(), split_point) > 1) { - /* At least two slots target other slots. 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. + /* 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. */ - auto next_target = sorted_slots.end(); - do { - /* 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; + if(next_target == split_point) UNLIKELY + break; - --next_target; - split_point = std::partition(sorted_slots.begin(), split_point, - [next_target](const EffectSlot *slot) noexcept -> bool - { return slot->Target != *next_target; }); - } while(split_point - sorted_slots.begin() > 1); + --next_target; + auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool + { return slot->Target != *next_target; }; + split_point = std::partition(sorted_slots.begin(), split_point, not_next); } } - for(const EffectSlot *slot : sorted_slots) + auto proc_slot = [SamplesToDo](const EffectSlot *slot) { EffectState *state{slot->mEffectState.get()}; state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); - } + }; + std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot); } /* Signal the event handler if there are any events to read. */ - RingBuffer *ring{ctx->mAsyncEvents.get()}; - if(ring->readSpace() > 0) + if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0) ctx->mEventSem.post(); - } + }; + const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)}; + std::for_each(contexts.begin(), contexts.end(), proc_context); } @@ -2144,25 +2149,47 @@ void Write(const al::span InBuffer, void *OutBuffer, cons ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); - const auto output = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep} - .subspan(Offset*FrameStep); - size_t c{0}; - for(const FloatBufferLine &inbuf : InBuffer) + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto output_ = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep}; + const auto output = output_.subspan(Offset*FrameStep); + + /* 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::fill(output.begin(), output.end(), SampleConv(0.0f)); + + auto outbase = output.begin(); + for(const auto &srcbuf : InBuffer) { - auto out = output.begin(); - auto conv_sample = [FrameStep,c,&out](const float s) noexcept + const auto src = al::span{srcbuf}.first(SamplesToDo); + auto out = outbase++; + + *out = SampleConv(src.front()); + std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept { - out[c] = SampleConv(s); out += ptrdiff_t(FrameStep); - }; - std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); - ++c; + *out = SampleConv(s); + }); } - if(const size_t extra{FrameStep - c}) +} + +template +void Write(const al::span InBuffer, al::span OutBuffers, + const size_t Offset, const size_t SamplesToDo) +{ + ASSUME(SamplesToDo > 0); + + auto srcbuf = InBuffer.cbegin(); + for(auto *dstbuf : OutBuffers) { - const auto silence = SampleConv(0.0f); - for(size_t i{0};i < SamplesToDo;++i) - std::fill_n(&output[i*FrameStep + c], extra, silence); + const auto src = al::span{*srcbuf}.first(SamplesToDo); + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto dst_ = al::span{static_cast(dstbuf), Offset+SamplesToDo}; + const auto dst = dst_.subspan(Offset); + std::transform(src.begin(), src.end(), dst.begin(), SampleConv); + ++srcbuf; } } @@ -2187,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples) * also guarantees a stable conversion. */ auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; - auto clockBase = mClockBase.load(std::memory_order_relaxed) + - std::chrono::seconds{samplesDone/Frequency}; - mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); - mClockBase.store(clockBase, std::memory_order_relaxed); + auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) + + seconds32{samplesDone/mSampleRate}; + mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed); + mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed); } /* Apply any needed post-process for finalizing the Dry mix to the RealOut @@ -2199,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples) postProcess(samplesToDo); /* Apply compression, limiting sample amplitude if needed or desired. */ - if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data()); + if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer); /* Apply delays and attenuation for mismatched speaker distances. */ if(ChannelDelays) @@ -2214,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples) return samplesToDo; } -void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) +void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) { FPUCtl mixer_mode{}; uint total{0}; @@ -2222,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span outBuffers, const uint num { const uint samplesToDo{renderSamples(todo)}; - auto srcbuf = RealOut.Buffer.cbegin(); - for(auto *dstbuf : outBuffers) + switch(FmtType) { - const auto dst = al::span{dstbuf, numSamples}.subspan(total); - std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin()); - ++srcbuf; +#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; } @@ -2266,7 +2299,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz } } -void DeviceBase::handleDisconnect(const char *msg, ...) +void DeviceBase::doDisconnect(std::string msg) { const auto mixLock = getWriteMixLock(); @@ -2274,21 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...) { AsyncEvent evt{std::in_place_type}; auto &disconnect = std::get(evt); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list args; - va_start(args, msg); - int msglen{vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args)}; - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - - if(msglen < 0 || static_cast(msglen) >= disconnect.msg.size()) - disconnect.msg[sizeof(disconnect.msg)-1] = 0; + disconnect.msg = std::move(msg); for(ContextBase *ctx : *mContexts.load()) { RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; + auto evt_data = ring->getWriteVector()[0]; if(evt_data.len > 0) { al::construct_at(reinterpret_cast(evt_data.buf), evt); diff --git a/libs/openal-soft/alc/alu.h b/libs/openal-soft/alc/alu.h index 33453487..6da8d917 100644 --- a/libs/openal-soft/alc/alu.h +++ b/libs/openal-soft/alc/alu.h @@ -2,20 +2,23 @@ #define ALU_H #include +#include #include -#include struct ALCcontext; struct ALCdevice; struct EffectSlot; -enum class StereoEncoding : uint8_t; +enum class StereoEncoding : std::uint8_t; +namespace al { +struct Device; +} // namespace al constexpr float GainMixMax{1000.0f}; /* +60dB */ -enum CompatFlags : uint8_t { +enum CompatFlags : std::uint8_t { ReverseX, ReverseY, ReverseZ, @@ -31,7 +34,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale); * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode); +void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); diff --git a/libs/openal-soft/alc/backends/alsa.cpp b/libs/openal-soft/alc/backends/alsa.cpp index 7626f1de..a241be7a 100644 --- a/libs/openal-soft/alc/backends/alsa.cpp +++ b/libs/openal-soft/alc/backends/alsa.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -38,16 +37,15 @@ #include #include -#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #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 "fmt/core.h" #include "ringbuffer.h" #include @@ -60,7 +58,7 @@ using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define ALSA_FUNCS(MAGIC) \ MAGIC(snd_strerror); \ MAGIC(snd_pcm_open); \ @@ -272,7 +270,7 @@ struct SndCtlCardInfo { SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; [[nodiscard]] - operator snd_ctl_card_info_t*() const noexcept { return mInfo; } + operator snd_ctl_card_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndPcmInfo { @@ -284,7 +282,7 @@ struct SndPcmInfo { SndPcmInfo& operator=(const SndPcmInfo&) = delete; [[nodiscard]] - operator snd_pcm_info_t*() const noexcept { return mInfo; } + operator snd_pcm_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndCtl { @@ -299,7 +297,7 @@ struct SndCtl { auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } [[nodiscard]] - operator snd_ctl_t*() const noexcept { return mHandle; } + operator snd_ctl_t*() const noexcept { return mHandle; } /* NOLINT(google-explicit-constructor) */ }; @@ -324,15 +322,15 @@ std::vector probe_devices(snd_pcm_stream_t stream) const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { - const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; - ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); + const auto spec = std::string_view{*customdevs}.substr(curpos, nextpos-curpos); + ERR("Invalid ALSA device specification \"{}\"", spec); } else { const std::string_view strview{*customdevs}; const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), strview.substr(seppos+1, nextpos-seppos-1)); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } if(nextpos < customdevs->length()) @@ -354,13 +352,13 @@ std::vector probe_devices(snd_pcm_stream_t stream) err = handle.open(name.c_str(), 0); if(err < 0) { - ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control open (hw:{}): {}", card, snd_strerror(err)); continue; } err = snd_ctl_card_info(handle, info); if(err < 0) { - ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control hardware info (hw:{}): {}", card, snd_strerror(err)); continue; } @@ -375,7 +373,7 @@ std::vector probe_devices(snd_pcm_stream_t stream) while(true) { if(snd_ctl_pcm_next_device(handle, &dev) < 0) - ERR("snd_ctl_pcm_next_device failed\n"); + ERR("snd_ctl_pcm_next_device failed"); if(dev < 0) break; snd_pcm_info_set_device(pcminfo, static_cast(dev)); @@ -385,42 +383,28 @@ std::vector probe_devices(snd_pcm_stream_t stream) if(err < 0) { if(err != -ENOENT) - ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); + ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err)); continue; } /* "prefix-cardid-dev" */ - name = prefix_name(stream); - name += '-'; - name += cardid; - name += '-'; - name += std::to_string(dev); - const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name) + name = fmt::format("{}-{}-{}", prefix_name(stream), cardid, dev); + const auto device_prefix = std::string{ConfigValueStr({}, "alsa"sv, name) .value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ - name = cardname; - name += ", "; - name += snd_pcm_info_get_name(pcminfo); - name += " (CARD="; - name += cardid; - name += ",DEV="; - name += std::to_string(dev); - name += ')'; + name = fmt::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo), + cardid, dev); /* "devprefixCARD=cardid,DEV=dev" */ - std::string device{device_prefix}; - device += "CARD="; - device += cardid; - device += ",DEV="; - device += std::to_string(dev); + auto device = fmt::format("{}CARD={},DEV={}", device_prefix, cardid, dev); const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } } if(err < 0) - ERR("snd_card_next failed: %s\n", snd_strerror(err)); + ERR("snd_card_next failed: {}", snd_strerror(err)); return devlist; } @@ -452,6 +436,17 @@ int verify_state(snd_pcm_t *handle) case SND_PCM_STATE_DISCONNECTED: return -ENODEV; + + /* ALSA headers have made this enum public, leaving us in a bind: use + * the enum despite being private and internal to the libasound, or + * ignore when an enum value isn't handled. We can't rely on it being + * declared either, since older headers don't have it and it could be + * removed in the future. We can't even really rely on its value, since + * being private/internal means it's subject to change, but this is the + * best we can do. + */ + case 1024 /*SND_PCM_STATE_PRIVATE1*/: + assert(state != 1024); } return state; @@ -459,7 +454,7 @@ int verify_state(snd_pcm_t *handle) struct AlsaPlayback final : public BackendBase { - AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; int mixerProc(); @@ -496,29 +491,29 @@ int AlsaPlayback::mixerProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; + const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; + const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); + ERR("Invalid state detected: {}", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)}; if(avails < 0) { - ERR("available update failed: %s\n", snd_strerror(static_cast(avails))); + ERR("available update failed: {}", snd_strerror(static_cast(avails))); continue; } snd_pcm_uframes_t avail{static_cast(avails)}; if(avail > buffer_size) { - WARN("available samples exceeds the buffer size\n"); + WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } @@ -531,12 +526,12 @@ int AlsaPlayback::mixerProc() int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { - ERR("start failed: %s\n", snd_strerror(err)); + ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); + ERR("Wait timeout... buffer size too low?"); continue; } avail -= avail%update_size; @@ -552,7 +547,7 @@ int AlsaPlayback::mixerProc() int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; if(err < 0) { - ERR("mmap begin error: %s\n", snd_strerror(err)); + ERR("mmap begin error: {}", snd_strerror(err)); break; } @@ -563,7 +558,7 @@ int AlsaPlayback::mixerProc() snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; if(commitres < 0 || static_cast(commitres) != frames) { - ERR("mmap commit error: %s\n", + ERR("mmap commit error: {}", snd_strerror(commitres >= 0 ? -EPIPE : static_cast(commitres))); break; } @@ -580,28 +575,28 @@ int AlsaPlayback::mixerNoMMapProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; - const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; + const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; + const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { - ERR("Invalid state detected: %s\n", snd_strerror(state)); - mDevice->handleDisconnect("Bad state: %s", snd_strerror(state)); + ERR("Invalid state detected: {}", snd_strerror(state)); + mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; if(avail < 0) { - ERR("available update failed: %s\n", snd_strerror(static_cast(avail))); + ERR("available update failed: {}", snd_strerror(static_cast(avail))); continue; } if(static_cast(avail) > buffer_size) { - WARN("available samples exceeds the buffer size\n"); + WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } @@ -613,12 +608,12 @@ int AlsaPlayback::mixerNoMMapProc() int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { - ERR("start failed: %s\n", snd_strerror(err)); + ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) - ERR("Wait timeout... buffer size too low?\n"); + ERR("Wait timeout... buffer size too low?"); continue; } @@ -675,7 +670,7 @@ void AlsaPlayback::open(std::string_view name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; driver = iter->device_name; } else @@ -684,13 +679,13 @@ void AlsaPlayback::open(std::string_view name) if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver.c_str()); + TRACE("Opening device \"{}\"", driver); snd_pcm_t *pcmHandle{}; int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) throw al::backend_exception{al::backend_error::NoDevice, - "Could not open ALSA device \"%s\"", driver.c_str()}; + "Could not open ALSA device \"{}\"", driver}; if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = pcmHandle; @@ -698,7 +693,7 @@ void AlsaPlayback::open(std::string_view name) /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); - mDevice->DeviceName = name; + mDeviceName = name; } bool AlsaPlayback::reset() @@ -729,15 +724,15 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; - uint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; - uint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; - uint rate{mDevice->Frequency}; + bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)}; + uint periodLen{static_cast(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)}; + uint bufferLen{static_cast(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate)}; + uint rate{mDevice->mSampleRate}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); @@ -787,21 +782,21 @@ bool AlsaPlayback::reset() else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) + 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\n"); + 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\n"); + WARN("Failed to enable ALSA resampler"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) - ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_hw_params_set_period_time_near failed: {}", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) - ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); + 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())); @@ -824,9 +819,9 @@ bool AlsaPlayback::reset() #undef CHECK sp = nullptr; - mDevice->BufferSize = static_cast(bufferSizeInFrames); - mDevice->UpdateSize = static_cast(periodSizeInFrames); - mDevice->Frequency = rate; + mDevice->mBufferSize = static_cast(bufferSizeInFrames); + mDevice->mUpdateSize = static_cast(periodSizeInFrames); + mDevice->mSampleRate = rate; setDefaultChannelOrder(); @@ -839,7 +834,7 @@ void AlsaPlayback::start() HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); @@ -850,7 +845,7 @@ void AlsaPlayback::start() int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { - auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize); + auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize); mBuffer.resize(static_cast(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } @@ -863,11 +858,11 @@ void AlsaPlayback::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(thread_func), this}; + mThread = std::thread{thread_func, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -880,31 +875,30 @@ void AlsaPlayback::stop() mBuffer.clear(); int err{snd_pcm_drop(mPcmHandle)}; if(err < 0) - ERR("snd_pcm_drop failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_drop failed: {}", snd_strerror(err)); } ClockLatency AlsaPlayback::getClockLatency() { - ClockLatency ret; - std::lock_guard dlock{mMutex}; + ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); + ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; - ret.Latency /= mDevice->Frequency; + ret.Latency /= mDevice->mSampleRate; return ret; } struct AlsaCapture final : public BackendBase { - AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; void open(std::string_view name) override; @@ -944,7 +938,7 @@ void AlsaCapture::open(std::string_view name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; driver = iter->device_name; } else @@ -954,10 +948,10 @@ void AlsaCapture::open(std::string_view name) driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver.c_str()); + TRACE("Opening device \"{}\"", driver); if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, - "Could not open ALSA device \"%s\"", driver.c_str()}; + "Could not open ALSA device \"{}\"", driver}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); @@ -988,16 +982,16 @@ void AlsaCapture::open(std::string_view name) break; } - snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, - 100u*mDevice->Frequency/1000u)}; - snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, - 25u*mDevice->Frequency/1000u)}; + snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->mBufferSize, + 100u*mDevice->mSampleRate/1000u)}; + snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->mBufferSize, + 25u*mDevice->mSampleRate/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ + throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); @@ -1008,11 +1002,11 @@ void AlsaCapture::open(std::string_view name) /* 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->Frequency, 0)); + 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\n"); + TRACE("Buffer too large, using intermediate ring buffer"); needring = true; CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames)); } @@ -1026,20 +1020,20 @@ void AlsaCapture::open(std::string_view name) hp = nullptr; if(needring) - mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false); + mRing = RingBuffer::Create(mDevice->mBufferSize, mDevice->frameSizeFromFmt(), false); - mDevice->DeviceName = name; + mDeviceName = name; } void AlsaCapture::start() { if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) - throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}", snd_strerror(err)}; if(int err{snd_pcm_start(mPcmHandle)}; err < 0) - throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", + throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: {}", snd_strerror(err)}; mDoCapture = true; @@ -1063,7 +1057,7 @@ void AlsaCapture::stop() mBuffer = std::move(temp); } if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) - ERR("drop failed: %s\n", snd_strerror(err)); + ERR("snd_pcm_drop failed: {}", snd_strerror(err)); mDoCapture = false; } @@ -1099,7 +1093,7 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples) amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(static_cast(amt))); + ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; @@ -1113,8 +1107,8 @@ void AlsaCapture::captureSamples(std::byte *buffer, uint samples) if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } /* If the amount available is less than what's asked, we lost it @@ -1139,7 +1133,7 @@ uint AlsaCapture::availableSamples() avail = snd_pcm_avail_update(mPcmHandle); if(avail < 0) { - ERR("avail update failed: %s\n", snd_strerror(static_cast(avail))); + ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast(avail))); avail = snd_pcm_recover(mPcmHandle, static_cast(avail), 1); if(avail >= 0) @@ -1152,29 +1146,29 @@ uint AlsaCapture::availableSamples() if(avail < 0) { const char *err{snd_strerror(static_cast(avail))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); } } if(!mRing) { - if(avail < 0) avail = 0; + avail = std::max(avail, 0); avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); - if(avail > mLastAvail) mLastAvail = avail; + mLastAvail = std::max(mLastAvail, avail); return static_cast(mLastAvail); } while(avail > 0) { auto vec = mRing->getWriteVector(); - if(vec.first.len == 0) break; + if(vec[0].len == 0) break; - snd_pcm_sframes_t amt{std::min(static_cast(vec.first.len), avail)}; - amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast(amt)); + snd_pcm_sframes_t amt{std::min(static_cast(vec[0].len), avail)}; + amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast(amt)); if(amt < 0) { - ERR("read error: %s\n", snd_strerror(static_cast(amt))); + ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; @@ -1189,8 +1183,8 @@ uint AlsaCapture::availableSamples() if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; - ERR("restore error: %s\n", err); - mDevice->handleDisconnect("Capture recovery failure: %s", err); + ERR("restore error: {}", err); + mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } avail = amt; @@ -1206,18 +1200,17 @@ uint AlsaCapture::availableSamples() ClockLatency AlsaCapture::getClockLatency() { - ClockLatency ret; - + ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { - ERR("Failed to get pcm delay: %s\n", snd_strerror(err)); + ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; - ret.Latency /= mDevice->Frequency; + ret.Latency /= mDevice->mSampleRate; return ret; } @@ -1227,13 +1220,13 @@ ClockLatency AlsaCapture::getClockLatency() bool AlsaBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!alsa_handle) { alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { - WARN("Failed to load %s\n", "libasound.so.2"); + WARN("Failed to load {}", "libasound.so.2"); return false; } @@ -1247,7 +1240,7 @@ bool AlsaBackendFactory::init() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(alsa_handle); alsa_handle = nullptr; return false; @@ -1261,26 +1254,23 @@ bool AlsaBackendFactory::init() bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string AlsaBackendFactory::probe(BackendType type) +auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; + switch(type) { case BackendType::Playback: PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/libs/openal-soft/alc/backends/alsa.h b/libs/openal-soft/alc/backends/alsa.h index b256dcf5..1327db8a 100644 --- a/libs/openal-soft/alc/backends/alsa.h +++ b/libs/openal-soft/alc/backends/alsa.h @@ -5,15 +5,15 @@ struct AlsaBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_ALSA_H */ diff --git a/libs/openal-soft/alc/backends/base.cpp b/libs/openal-soft/alc/backends/base.cpp index e5e28201..9d9ce4a0 100644 --- a/libs/openal-soft/alc/backends/base.cpp +++ b/libs/openal-soft/alc/backends/base.cpp @@ -3,36 +3,18 @@ #include "base.h" -#include #include #include +#include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include "albit.h" -#include "core/logging.h" -#endif - -#include "atomic.h" #include "core/devformat.h" namespace al { +auto backend_exception::make_string(fmt::string_view fmt, fmt::format_args args) -> std::string +{ return fmt::vformat(fmt, std::move(args)); } -backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} -{ - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ -} backend_exception::~backend_exception() = default; - } // namespace al @@ -60,8 +42,8 @@ ClockLatency BackendBase::getClockLatency() * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mDevice->mBufferSize - mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } @@ -126,6 +108,24 @@ void BackendBase::setDefaultWFXChannelOrder() const 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; @@ -179,6 +179,24 @@ void BackendBase::setDefaultChannelOrder() const 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; diff --git a/libs/openal-soft/alc/backends/base.h b/libs/openal-soft/alc/backends/base.h index b575e166..4e59836d 100644 --- a/libs/openal-soft/alc/backends/base.h +++ b/libs/openal-soft/alc/backends/base.h @@ -5,13 +5,14 @@ #include #include #include -#include #include #include +#include +#include "alc/events.h" #include "core/device.h" #include "core/except.h" -#include "alc/events.h" +#include "fmt/core.h" using uint = unsigned int; @@ -34,10 +35,17 @@ struct BackendBase { virtual ClockLatency getClockLatency(); DeviceBase *const mDevice; + std::string mDeviceName; - BackendBase(DeviceBase *device) noexcept : mDevice{device} { } + BackendBase() = delete; + BackendBase(const BackendBase&) = delete; + BackendBase(BackendBase&&) = delete; + explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; + void operator=(const BackendBase&) = delete; + void operator=(BackendBase&&) = delete; + protected: /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ void setDefaultChannelOrder() const; @@ -64,18 +72,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) struct BackendFactory { + BackendFactory() = default; + BackendFactory(const BackendFactory&) = delete; + BackendFactory(BackendFactory&&) = delete; virtual ~BackendFactory() = default; - virtual bool init() = 0; + void operator=(const BackendFactory&) = delete; + void operator=(BackendFactory&&) = delete; - virtual bool querySupport(BackendType type) = 0; + virtual auto init() -> bool = 0; - virtual alc::EventSupport queryEventSupport(alc::EventType, BackendType) + virtual auto querySupport(BackendType type) -> bool = 0; + + virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport { return alc::EventSupport::NoSupport; } - virtual std::string probe(BackendType type) = 0; + virtual auto enumerate(BackendType type) -> std::vector = 0; - virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; + virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0; }; namespace al { @@ -89,15 +103,20 @@ enum class backend_error { class backend_exception final : public base_exception { backend_error mErrorCode; + static auto make_string(fmt::string_view fmt, fmt::format_args args) -> std::string; + public: -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - backend_exception(backend_error code, const char *msg, ...); + template + backend_exception(backend_error code, fmt::format_string fmt, Args&& ...args) + : base_exception{make_string(fmt, fmt::make_format_args(args...))}, mErrorCode{code} + { } + backend_exception(const backend_exception&) = default; + backend_exception(backend_exception&&) = default; ~backend_exception() override; + backend_exception& operator=(const backend_exception&) = default; + backend_exception& operator=(backend_exception&&) = default; + [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; diff --git a/libs/openal-soft/alc/backends/coreaudio.cpp b/libs/openal-soft/alc/backends/coreaudio.cpp index 2067d279..138859cb 100644 --- a/libs/openal-soft/alc/backends/coreaudio.cpp +++ b/libs/openal-soft/alc/backends/coreaudio.cpp @@ -24,7 +24,9 @@ #include #include +#include #include +#include #include #include #include @@ -32,13 +34,13 @@ #include #include #include -#include #include "alnumeric.h" #include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include @@ -56,10 +58,40 @@ 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]{}; - constexpr FourCCPrinter(UInt32 code) noexcept + explicit constexpr FourCCPrinter(UInt32 code) noexcept { for(size_t i{0};i < sizeof(UInt32);++i) { @@ -73,7 +105,7 @@ struct FourCCPrinter { code >>= 8; } } - constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast(code)} { } + explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast(code)} { } constexpr const char *c_str() const noexcept { return mString; } }; @@ -159,19 +191,19 @@ std::string GetDeviceName(AudioDeviceID devId) /* Clear extraneous nul chars that may have been written with the name * string, and return it. */ - while(!devname.back()) + while(!devname.empty() && !devname.back()) devname.pop_back(); return devname; } -UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) +auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32 { - UInt32 propSize{}; + auto propSize = UInt32{}; auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } @@ -183,15 +215,14 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + ERR("kAudioDevicePropertyStreamConfiguration query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } - UInt32 numChannels{0}; + auto numChannels = UInt32{0}; for(size_t i{0};i < buflist->mNumberBuffers;++i) numChannels += buflist->mBuffers[i].mNumberChannels; - return numChannels; } @@ -201,14 +232,14 @@ void EnumerateDevices(std::vector &list, bool isCapture) UInt32 propSize{}; if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize)) { - ERR("Failed to get device list size: %u\n", err); + 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: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("Failed to get device list: '{}' ({})", FourCCPrinter{err}.c_str(), err); return; } @@ -223,7 +254,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) { newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)}); const auto &entry = newdevs.back(); - TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } for(const AudioDeviceID devId : devIds) { @@ -240,7 +271,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) { newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)}); const auto &entry = newdevs.back(); - TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId); + TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } } @@ -255,14 +286,12 @@ void EnumerateDevices(std::vector &list, bool isCapture) { return entry.mName == curitem->mName; }; if(std::find_if(newdevs.begin(), curitem, check_match) != curitem) { - std::string name{curitem->mName}; - size_t count{1}; + auto name = std::string{curitem->mName}; + auto count = 1_uz; auto check_name = [&name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; do { - name = curitem->mName; - name += " #"; - name += std::to_string(++count); + name = fmt::format("{} #{}", curitem->mName, ++count); } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); curitem->mName = std::move(name); } @@ -277,18 +306,18 @@ struct DeviceHelper { DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) - ERR("AudioObjectAddPropertyListener fail: %d", status); + ERR("AudioObjectAddPropertyListener fail: {}", status); } ~DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) - ERR("AudioObjectRemovePropertyListener fail: %d", status); + ERR("AudioObjectRemovePropertyListener fail: {}", status); } static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, @@ -322,7 +351,7 @@ static constexpr char ca_device[] = "CoreAudio Default"; struct CoreAudioPlayback final : public BackendBase { - CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -377,7 +406,7 @@ void CoreAudioPlayback::open(std::string_view name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } @@ -385,8 +414,8 @@ void CoreAudioPlayback::open(std::string_view name) if(name.empty()) name = ca_device; else if(name != ca_device) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; #endif /* open the default output unit */ @@ -410,7 +439,7 @@ void CoreAudioPlayback::open(std::string_view name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -421,7 +450,7 @@ void CoreAudioPlayback::open(std::string_view name) err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "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. @@ -435,7 +464,7 @@ void CoreAudioPlayback::open(std::string_view name) #if CAN_ENUMERATE if(!name.empty()) - mDevice->DeviceName = name; + mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; @@ -444,8 +473,8 @@ void CoreAudioPlayback::open(std::string_view name) kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; - if(!devname.empty()) mDevice->DeviceName = std::move(devname); - else mDevice->DeviceName = "Unknown Device Name"; + if(!devname.empty()) mDeviceName = std::move(devname); + else mDeviceName = "Unknown Device Name"; } if(audioDevice != kAudioDeviceUnknown) @@ -454,16 +483,16 @@ void CoreAudioPlayback::open(std::string_view name) err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, kAudioObjectPropertyElementMaster, sizeof(type), &type); if(err != noErr) - ERR("Failed to get audio device type: %u\n", err); + WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err); else { - TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + TRACE("Got device type '{}'", FourCCPrinter{type}.c_str()); mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); } } #else - mDevice->DeviceName = name; + mDeviceName = name; #endif } @@ -471,7 +500,7 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; @@ -480,33 +509,75 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } -#if 0 - TRACE("Output streamFormat of default output unit -\n"); - TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket); - TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame); - TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel); - TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket); - TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame); - TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); -#endif - /* Use the sample rate from the output unit's current parameters, but reset * everything else. */ - if(mDevice->Frequency != streamFormat.mSampleRate) + if(mDevice->mSampleRate != streamFormat.mSampleRate) { - mDevice->BufferSize = static_cast(mDevice->BufferSize*streamFormat.mSampleRate/ - mDevice->Frequency + 0.5); - mDevice->Frequency = static_cast(streamFormat.mSampleRate); + mDevice->mBufferSize = static_cast(mDevice->mBufferSize*streamFormat.mSampleRate/ + mDevice->mSampleRate + 0.5); + mDevice->mSampleRate = static_cast(streamFormat.mSampleRate); } - /* FIXME: How to tell what channels are what in the output device, and how - * to specify what we're giving? e.g. 6.0 vs 5.1 + struct ChannelMap { + DevFmtChannels fmt; + al::span map; + bool is_51rear; + }; + + static constexpr std::array chanmaps{{ + { DevFmtX71, X71ChanMap, false }, + { DevFmtX61, X61ChanMap, false }, + { DevFmtX51, X51ChanMap, false }, + { DevFmtX51, X51RearChanMap, true }, + { DevFmtQuad, QuadChanMap, false }, + { DevFmtStereo, StereoChanMap, false }, + { DevFmtMono, MonoChanMap, false } + }}; + + if(!mDevice->Flags.test(ChannelsRequest)) + { + auto propSize = UInt32{}; + auto writable = Boolean{}; + + err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, OutputElement, &propSize, &writable); + if(err == noErr) + { + auto layout_data = std::make_unique(propSize); + auto *layout = reinterpret_cast(layout_data.get()); + + err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, + kAudioUnitScope_Output, OutputElement, layout, &propSize); + if(err == noErr) + { + auto descs = al::span{std::data(layout->mChannelDescriptions), + layout->mNumberChannelDescriptions}; + auto labels = std::vector(descs.size()); + + std::transform(descs.begin(), descs.end(), labels.begin(), + std::mem_fn(&AudioChannelDescription::mChannelLabel)); + sort(labels.begin(), labels.end()); + + auto check_labels = [&labels](const ChannelMap &chanmap) -> bool + { + return std::includes(labels.begin(), labels.end(), chanmap.map.begin(), + chanmap.map.end()); + }; + auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels); + if(chaniter != chanmaps.cend()) + mDevice->FmtChans = chaniter->fmt; + } + } + } + + /* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL + * device's channel configuration. */ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); @@ -548,7 +619,7 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -566,7 +637,7 @@ bool CoreAudioPlayback::reset() kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -575,7 +646,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } @@ -587,19 +658,19 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } struct CoreAudioCapture final : public BackendBase { - CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioCapture() override; OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, @@ -650,7 +721,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err); return err; } @@ -676,7 +747,7 @@ void CoreAudioCapture::open(std::string_view name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } @@ -684,8 +755,8 @@ void CoreAudioCapture::open(std::string_view name) if(name.empty()) name = ca_device; else if(name != ca_device) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; #endif AudioComponentDescription desc{}; @@ -709,7 +780,7 @@ void CoreAudioCapture::open(std::string_view name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; @@ -717,7 +788,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn on AudioUnit input @@ -726,7 +797,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE @@ -745,7 +816,7 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; @@ -753,14 +824,14 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + "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: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; @@ -769,7 +840,7 @@ void CoreAudioCapture::open(std::string_view name) InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -822,15 +893,16 @@ void CoreAudioCapture::open(std::string_view name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", + 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->Frequency; + requestedFormat.mSampleRate = mDevice->mSampleRate; requestedFormat.mFormatID = kAudioFormatLinearPCM; requestedFormat.mReserved = 0; requestedFormat.mFramesPerPacket = 1; @@ -850,18 +922,18 @@ void CoreAudioCapture::open(std::string_view name) InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "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->Frequency}; - auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->BufferSize*srateScale)), + double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate}; + auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->mBufferSize*srateScale)), static_cast(outputFormat.mSampleRate)/10_u64); FrameCount64 += MaxResamplerPadding; if(FrameCount64 > std::numeric_limits::max()) throw al::backend_exception{al::backend_error::DeviceError, - "Calculated frame count is too large: %" PRIu64, FrameCount64}; + "Calculated frame count is too large: {}", FrameCount64}; UInt32 outputFrameCount{}; propertySize = sizeof(outputFrameCount); @@ -869,7 +941,7 @@ void CoreAudioCapture::open(std::string_view name) 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: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "Could not get input frame count: '{}' ({})", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); @@ -877,14 +949,14 @@ void CoreAudioCapture::open(std::string_view name) mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ - if(outputFormat.mSampleRate != mDevice->Frequency) + if(outputFormat.mSampleRate != mDevice->mSampleRate) mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), - mDevice->Frequency, Resampler::FastBSinc24); + mDevice->mSampleRate, Resampler::FastBSinc24); #if CAN_ENUMERATE if(!name.empty()) - mDevice->DeviceName = name; + mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; @@ -893,11 +965,11 @@ void CoreAudioCapture::open(std::string_view name) kAudioUnitScope_Global, InputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; - if(!devname.empty()) mDevice->DeviceName = std::move(devname); - else mDevice->DeviceName = "Unknown Device Name"; + if(!devname.empty()) mDeviceName = std::move(devname); + else mDeviceName = "Unknown Device Name"; } #else - mDevice->DeviceName = name; + mDeviceName = name; #endif } @@ -907,14 +979,14 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; + "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); + ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) @@ -926,16 +998,16 @@ void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) } auto rec_vec = mRing->getReadVector(); - const void *src0{rec_vec.first.buf}; - auto src0len = static_cast(rec_vec.first.len); + const void *src0{rec_vec[0].buf}; + auto src0len = static_cast(rec_vec[0].len); uint got{mConverter->convert(&src0, &src0len, buffer, samples)}; - size_t total_read{rec_vec.first.len - src0len}; - if(got < samples && !src0len && rec_vec.second.len > 0) + size_t total_read{rec_vec[0].len - src0len}; + if(got < samples && !src0len && rec_vec[1].len > 0) { - const void *src1{rec_vec.second.buf}; - auto src1len = static_cast(rec_vec.second.len); + const void *src1{rec_vec[1].buf}; + auto src1len = static_cast(rec_vec[1].len); got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); - total_read += rec_vec.second.len - src1len; + total_read += rec_vec[1].len - src1len; } mRing->readAdvance(total_read); @@ -966,23 +1038,23 @@ bool CoreAudioBackendFactory::init() bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string CoreAudioBackendFactory::probe(BackendType type) +auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; #if CAN_ENUMERATE auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { 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; } @@ -993,8 +1065,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(ca_device, sizeof(ca_device)); + outnames.emplace_back(ca_device); break; } #endif diff --git a/libs/openal-soft/alc/backends/coreaudio.h b/libs/openal-soft/alc/backends/coreaudio.h index 6ea4307c..26c2aaf9 100644 --- a/libs/openal-soft/alc/backends/coreaudio.h +++ b/libs/openal-soft/alc/backends/coreaudio.h @@ -5,17 +5,17 @@ struct CoreAudioBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_COREAUDIO_H */ diff --git a/libs/openal-soft/alc/backends/dsound.cpp b/libs/openal-soft/alc/backends/dsound.cpp index 82a59dbe..2bb7f5f5 100644 --- a/libs/openal-soft/alc/backends/dsound.cpp +++ b/libs/openal-soft/alc/backends/dsound.cpp @@ -37,23 +37,20 @@ #include #include #include -#include #include -#include #include #include #include -#include "albit.h" #include "alnumeric.h" #include "alspan.h" -#include "alstring.h" #include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" @@ -92,10 +89,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0 namespace { -#define DEVNAME_HEAD "OpenAL Soft on " - - -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD void *ds_handle; HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); @@ -148,24 +142,19 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi return TRUE; auto& devices = *static_cast*>(data); - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; + const auto basename = wstr_to_utf8(desc); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(devices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - devices.emplace_back(std::move(newname), *guid); - const DevMap &newentry = devices.back(); + newname = fmt::format("{} #{}", basename, ++count); + const DevMap &newentry = devices.emplace_back(std::move(newname), *guid); OLECHAR *guidstr{nullptr}; HRESULT hr{StringFromCLSID(*guid, &guidstr)}; if(SUCCEEDED(hr)) { - TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr); + TRACE("Got device \"{}\", GUID \"{}\"", newentry.name, wstr_to_utf8(guidstr)); CoTaskMemFree(guidstr); } @@ -174,7 +163,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi struct DSoundPlayback final : public BackendBase { - DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; int mixerProc(); @@ -217,14 +206,15 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() HRESULT err{mBuffer->GetCaps(&DSBCaps)}; if(FAILED(err)) { - ERR("Failed to get buffer caps: 0x%lx\n", err); - mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err); + ERR("Failed to get buffer caps: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failure retrieving playback buffer info: {:#x}", + as_unsigned(err)); return 1; } const size_t FrameStep{mDevice->channelsFromFmt()}; uint FrameSize{mDevice->frameSizeFromFmt()}; - DWORD FragSize{mDevice->UpdateSize * FrameSize}; + DWORD FragSize{mDevice->mUpdateSize * FrameSize}; bool Playing{false}; DWORD LastCursor{0u}; @@ -244,8 +234,9 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); if(FAILED(err)) { - ERR("Failed to play buffer: 0x%lx\n", err); - mDevice->handleDisconnect("Failure starting playback: 0x%lx", err); + ERR("Failed to play buffer: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failure starting playback: {:#x}", + as_unsigned(err)); return 1; } Playing = true; @@ -253,7 +244,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); if(avail != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); + ERR("WaitForSingleObjectEx error: {:#x}", avail); continue; } avail -= avail%FragSize; @@ -266,7 +257,7 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() // If the buffer is lost, restore it and lock if(err == DSERR_BUFFERLOST) { - WARN("Buffer lost, restoring...\n"); + WARN("Buffer lost, restoring..."); err = mBuffer->Restore(); if(SUCCEEDED(err)) { @@ -276,22 +267,19 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() &WritePtr2, &WriteCnt2, 0); } } - - if(SUCCEEDED(err)) + if(FAILED(err)) { - mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); - if(WriteCnt2 > 0) - mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); - - mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); - } - else - { - ERR("Buffer lock error: %#lx\n", err); - mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err); + ERR("Buffer lock error: {:#x}", as_unsigned(err)); + mDevice->handleDisconnect("Failed to lock output buffer: {:#x}", as_unsigned(err)); return 1; } + mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); + if(WriteCnt2 > 0) + mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); + + mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); + // Update old write cursor location LastCursor += WriteCnt1+WriteCnt2; LastCursor %= DSBCaps.dwBufferBytes; @@ -309,7 +297,7 @@ void DSoundPlayback::open(std::string_view name) ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); + ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; @@ -331,7 +319,7 @@ void DSoundPlayback::open(std::string_view name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; } guid = &iter->guid; } @@ -350,15 +338,15 @@ void DSoundPlayback::open(std::string_view name) if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - 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); - mDevice->DeviceName = name; + mDeviceName = name; } bool DSoundPlayback::reset() @@ -393,7 +381,7 @@ bool DSoundPlayback::reset() HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to get speaker config: 0x%08lx", hr}; + "Failed to get speaker config: {:#x}", as_unsigned(hr)}; speakers = DSSPEAKER_CONFIG(speakers); if(!mDevice->Flags.test(ChannelsRequest)) @@ -409,7 +397,7 @@ bool DSoundPlayback::reset() else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) mDevice->FmtChans = DevFmtX71; else - ERR("Unknown system speaker config: 0x%lx\n", speakers); + ERR("Unknown system speaker config: {:#x}", speakers); } mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; @@ -424,81 +412,83 @@ bool DSoundPlayback::reset() case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX7144: mDevice->FmtChans = DevFmtX714; + /* fall-through */ case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; } -retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); - OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); - OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = 0; + do { + hr = S_OK; + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); + OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + OutputType.Format.cbSize = 0; - if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - 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) + if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); - } - if(SUCCEEDED(hr)) - hr = mPrimaryBuffer->SetFormat(&OutputType.Format); - } + 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; - if(SUCCEEDED(hr)) - { - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + mPrimaryBuffer = nullptr; + } + else + { + if(SUCCEEDED(hr) && !mPrimaryBuffer) + { + DSBUFFERDESC DSBDescription{}; + DSBDescription.dwSize = sizeof(DSBDescription); + DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); + } + if(SUCCEEDED(hr)) + hr = mPrimaryBuffer->SetFormat(&OutputType.Format); + } + + if(FAILED(hr)) + break; + + uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; - mDevice->BufferSize = mDevice->UpdateSize * num_updates; + mDevice->mBufferSize = mDevice->mUpdateSize * num_updates; DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; - DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; + DSBDescription.dwBufferBytes = mDevice->mBufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); - if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - } + if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat) + break; + mDevice->FmtType = DevFmtShort; + } while(FAILED(hr)); if(SUCCEEDED(hr)) { hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; + uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; assert(num_updates <= MAX_UPDATES); - std::array nots; + std::array nots{}; for(uint i{0};i < num_updates;++i) { - nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; + nots[i].dwOffset = i * mDevice->mUpdateSize * OutputType.Format.nBlockAlign; nots[i].hEventNotify = mNotifyEvent; } if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) @@ -524,11 +514,11 @@ void DSoundPlayback::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this}; + mThread = std::thread{&DSoundPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -543,7 +533,7 @@ void DSoundPlayback::stop() struct DSoundCapture final : public BackendBase { - DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; void open(std::string_view name) override; @@ -580,7 +570,7 @@ void DSoundCapture::open(std::string_view name) ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) - ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); + ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; @@ -602,7 +592,7 @@ void DSoundCapture::open(std::string_view name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; } guid = &iter->guid; } @@ -612,9 +602,9 @@ void DSoundCapture::open(std::string_view name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType)); + WARN("{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)); throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; case DevFmtUByte: case DevFmtShort: @@ -633,10 +623,11 @@ void DSoundCapture::open(std::string_view name) 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("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + WARN("{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)); + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -645,10 +636,11 @@ void DSoundCapture::open(std::string_view name) InputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); - InputType.Format.nSamplesPerSec = mDevice->Frequency; + 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; @@ -661,7 +653,7 @@ void DSoundCapture::open(std::string_view name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); @@ -674,7 +666,7 @@ void DSoundCapture::open(std::string_view name) if(SUCCEEDED(hr)) mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) - mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); + mRing = RingBuffer::Create(mDevice->mBufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { @@ -682,14 +674,14 @@ void DSoundCapture::open(std::string_view name) mDSCbuffer = nullptr; mDSC = nullptr; - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(hr)}; } mBufferBytes = DSCBDescription.dwBufferBytes; setDefaultWFXChannelOrder(); - mDevice->DeviceName = name; + mDeviceName = name; } void DSoundCapture::start() @@ -697,7 +689,7 @@ void DSoundCapture::start() const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, - "Failure starting capture: 0x%lx", hr}; + "Failure starting capture: {:#x}", as_unsigned(hr)}; } void DSoundCapture::stop() @@ -705,8 +697,8 @@ void DSoundCapture::stop() HRESULT hr{mDSCbuffer->Stop()}; if(FAILED(hr)) { - ERR("stop failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr); + ERR("stop failed: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr)); } } @@ -743,8 +735,8 @@ uint DSoundCapture::availableSamples() if(FAILED(hr)) { - ERR("update failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr); + ERR("update failed: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failure retrieving capture data: {:#x}", as_unsigned(hr)); } return static_cast(mRing->readSpace()); @@ -761,13 +753,13 @@ BackendFactory &DSoundBackendFactory::getFactory() bool DSoundBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!ds_handle) { ds_handle = LoadLib("dsound.dll"); if(!ds_handle) { - ERR("Failed to load dsound.dll\n"); + ERR("Failed to load dsound.dll"); return false; } @@ -793,35 +785,29 @@ bool DSoundBackendFactory::init() bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string DSoundBackendFactory::probe(BackendType type) +auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; /* Initialize COM to prevent name truncation */ ComWrapper com{}; - HRESULT hr; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); + if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr)) + ERR("Error enumerating DirectSound playback devices: {:#x}", as_unsigned(hr)); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) - ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); + if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr)) + ERR("Error enumerating DirectSound capture devices: {:#x}", as_unsigned(hr)); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/libs/openal-soft/alc/backends/dsound.h b/libs/openal-soft/alc/backends/dsound.h index 787f227a..33adbf29 100644 --- a/libs/openal-soft/alc/backends/dsound.h +++ b/libs/openal-soft/alc/backends/dsound.h @@ -5,15 +5,15 @@ struct DSoundBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_DSOUND_H */ diff --git a/libs/openal-soft/alc/backends/jack.cpp b/libs/openal-soft/alc/backends/jack.cpp index 0e972500..fb84287f 100644 --- a/libs/openal-soft/alc/backends/jack.cpp +++ b/libs/openal-soft/alc/backends/jack.cpp @@ -29,19 +29,17 @@ #include #include #include -#include #include -#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" #include "alsem.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/format.h" #include "ringbuffer.h" #include @@ -52,7 +50,7 @@ namespace { using namespace std::string_view_literals; -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ MAGIC(jack_client_close); \ @@ -109,10 +107,12 @@ jack_options_t ClientOptions = JackNullOption; bool jack_load() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!jack_handle) { -#ifdef _WIN32 +#if defined(_WIN64) +#define JACKLIB "libjack64.dll" +#elif defined(_WIN32) #define JACKLIB "libjack.dll" #else #define JACKLIB "libjack.so.0" @@ -120,7 +120,7 @@ bool jack_load() jack_handle = LoadLib(JACKLIB); if(!jack_handle) { - WARN("Failed to load %s\n", JACKLIB); + WARN("Failed to load {}", JACKLIB); return false; } @@ -138,7 +138,7 @@ bool jack_load() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(jack_handle); jack_handle = nullptr; return false; @@ -159,11 +159,19 @@ struct DeviceEntry { std::string mName; std::string mPattern; + DeviceEntry() = default; + DeviceEntry(const DeviceEntry&) = default; + DeviceEntry(DeviceEntry&&) = default; template DeviceEntry(T&& name, U&& pattern) : mName{std::forward(name)}, mPattern{std::forward(pattern)} { } + ~DeviceEntry(); + + DeviceEntry& operator=(const DeviceEntry&) = default; + DeviceEntry& operator=(DeviceEntry&&) = default; }; +DeviceEntry::~DeviceEntry() = default; std::vector PlaybackList; @@ -181,21 +189,21 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) if(seppos == 0 || seppos >= portname.size()) continue; - const std::string_view portdev{ports[i], seppos}; + const auto portdev = portname.substr(0, seppos); auto check_name = [portdev](const DeviceEntry &entry) -> bool { return entry.mName == portdev; }; if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; - const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); - TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + const auto &entry = list.emplace_back(portdev, fmt::format("{}:", portdev)); + TRACE("Got device: {} = {}", entry.mName, entry.mPattern); } /* There are ports but couldn't get device names from them. Add a * generic entry. */ if(ports[0] && list.empty()) { - WARN("No device names found in available ports, adding a generic name.\n"); + WARN("No device names found in available ports, adding a generic name."); list.emplace_back("JACK"sv, ""sv); } } @@ -208,8 +216,8 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) size_t seppos{listopt->find('=', strpos)}; if(seppos >= nextpos || seppos == strpos) { - const std::string entry{listopt->substr(strpos, nextpos-strpos)}; - ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + 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; @@ -227,14 +235,13 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) { /* If so, replace the name with this custom one. */ itemmatch->mName = name; - TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), - itemmatch->mPattern.c_str()); + TRACE("Customized device name: {} = {}", itemmatch->mName, itemmatch->mPattern); } else { /* Otherwise, add a new device entry. */ const auto &entry = list.emplace_back(name, pattern); - TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); + TRACE("Got custom device: {} = {}", entry.mName, entry.mPattern); } if(nextpos != std::string::npos) ++nextpos; @@ -270,7 +277,7 @@ void EnumerateDevices(jack_client_t *client, std::vector &list) struct JackPlayback final : public BackendBase { - JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; int processRt(jack_nframes_t numframes) noexcept; @@ -322,22 +329,22 @@ JackPlayback::~JackPlayback() int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::array out; - size_t numchans{0}; + auto outptrs = std::array{}; + auto numchans = size_t{0}; for(auto port : mPort) { if(!port || numchans == mDevice->RealOut.Buffer.size()) break; - out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + outptrs[numchans++] = jack_port_get_buffer(port, numframes); } + const auto dst = al::span{outptrs}.first(numchans); if(mPlaying.load(std::memory_order_acquire)) LIKELY - mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); + mDevice->renderSamples(dst, static_cast(numframes)); else { - auto clear_buf = [numframes](float *outbuf) -> void - { std::fill_n(outbuf, numframes, 0.0f); }; - std::for_each(out.begin(), out.begin()+numchans, clear_buf); + std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void + { std::fill_n(static_cast(outbuf), numframes, 0.0f); }); } return 0; @@ -354,43 +361,38 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept out[numchans++] = {static_cast(jack_port_get_buffer(port, numframes)), numframes}; } - jack_nframes_t total{0}; + size_t total{0}; if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); - jack_nframes_t todo{std::min(numframes, static_cast(data.first.len))}; - auto firstin = al::span{reinterpret_cast(data.first.buf), todo}; - for(size_t c{0};c < numchans;++c) - { - auto in = firstin.cbegin(); - auto deinterlace_input = [&in,c,numchans]() noexcept -> float - { - const float ret{in[c]}; - in += ptrdiff_t(numchans); - return ret; - }; - std::generate_n(out[c].begin(), todo, deinterlace_input); - out[c] = out[c].subspan(todo); - } - total += todo; + const auto update_size = size_t{mDevice->mUpdateSize}; - todo = std::min(numframes-total, static_cast(data.second.len)); - if(todo > 0) + const auto outlen = size_t{numframes / update_size}; + const auto len1 = size_t{std::min(data[0].len/update_size, outlen)}; + const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)}; + + auto src = al::span{reinterpret_cast(data[0].buf), update_size*len1*numchans}; + for(size_t i{0};i < len1;++i) { - auto secondin = al::span{reinterpret_cast(data.second.buf), todo}; for(size_t c{0};c < numchans;++c) { - auto in = secondin.cbegin(); - auto deinterlace_input = [&in,c,numchans]() noexcept -> float - { - float ret{in[c]}; - in += ptrdiff_t(numchans); - return ret; - }; - std::generate_n(out[c].begin(), todo, deinterlace_input); - out[c] = out[c].subspan(todo); + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); } - total += todo; + total += update_size; + } + + src = al::span{reinterpret_cast(data[1].buf), update_size*len2*numchans}; + for(size_t i{0};i < len2;++i) + { + for(size_t c{0};c < numchans;++c) + { + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); + } + total += update_size; } mRing->readAdvance(total); @@ -412,29 +414,52 @@ int JackPlayback::mixerProc() SetRTPriority(); althrd_setname(GetMixerThreadName()); - const size_t frame_step{mDevice->channelsFromFmt()}; + const auto update_size = uint{mDevice->mUpdateSize}; + const auto num_channels = size_t{mDevice->channelsFromFmt()}; + auto outptrs = std::vector(num_channels); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - if(mRing->writeSpace() < mDevice->UpdateSize) + if(mRing->writeSpace() < update_size) { mSem.wait(); continue; } auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - todo -= todo%mDevice->UpdateSize; - - const auto len1 = static_cast(std::min(data.first.len, todo)); - const auto len2 = static_cast(std::min(data.second.len, todo-len1)); + const auto len1 = size_t{data[0].len / update_size}; + const auto len2 = size_t{data[1].len / update_size}; std::lock_guard dlock{mMutex}; - mDevice->renderSamples(data.first.buf, len1, frame_step); + auto buffer = al::span{reinterpret_cast(data[0].buf), data[0].len*num_channels}; + auto bufiter = buffer.begin(); + for(size_t i{0};i < len1;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } if(len2 > 0) - mDevice->renderSamples(data.second.buf, len2, frame_step); - mRing->writeAdvance(todo); + { + buffer = al::span{reinterpret_cast(data[1].buf), data[1].len*num_channels}; + bufiter = buffer.begin(); + for(size_t i{0};i < len2;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } + } + mRing->writeAdvance((len1+len2) * update_size); } return 0; @@ -452,13 +477,14 @@ void JackPlayback::open(std::string_view name) 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: 0x%02x", status}; + "Failed to open client connection: {:#02x}", + as_unsigned(al::to_underlying(status))}; if((status&JackServerStarted)) - TRACE("JACK server started\n"); + TRACE("JACK server started"); if((status&JackNameNotUnique)) { client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got '%s' instead\n", client_name); + TRACE("Client name not unique, got '{}' instead", client_name); } } @@ -477,11 +503,11 @@ void JackPlayback::open(std::string_view name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; mPortPattern = iter->mPattern; } - mDevice->DeviceName = name; + mDeviceName = name; } bool JackPlayback::reset() @@ -491,37 +517,38 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); - mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); + 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->Frequency = jack_get_sample_rate(mClient); - mDevice->UpdateSize = jack_get_buffer_size(mClient); + 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->BufferSize = mDevice->UpdateSize * 2; + mDevice->mBufferSize = mDevice->mUpdateSize * 2; } else { - const std::string_view devname{mDevice->DeviceName}; - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + const auto devname = std::string_view{mDevice->mDeviceName}; + auto bufsize = ConfigValueUInt(devname, "jack", "buffer-size") + .value_or(mDevice->mUpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); + mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; } /* Force 32-bit float output. */ mDevice->FmtType = DevFmtFloat; int port_num{0}; - auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); - auto bad_port = mPort.begin(); - while(bad_port != ports_end) + auto ports = al::span{mPort}.first(mDevice->channelsFromFmt()); + auto bad_port = ports.begin(); + while(bad_port != ports.end()) { std::string name{"channel_" + std::to_string(++port_num)}; *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, @@ -529,17 +556,17 @@ bool JackPlayback::reset() if(!*bad_port) break; ++bad_port; } - if(bad_port != ports_end) + if(bad_port != ports.end()) { - ERR("Failed to register enough JACK ports for %s output\n", + ERR("Failed to register enough JACK ports for {} output", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == mPort.begin()) return false; + if(bad_port == ports.begin()) return false; - if(bad_port == mPort.begin()+1) + if(bad_port == ports.begin()+1) mDevice->FmtChans = DevFmtMono; else { - ports_end = mPort.begin()+2; + const auto ports_end = ports.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); @@ -559,7 +586,7 @@ void JackPlayback::start() if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const std::string_view devname{mDevice->DeviceName}; + const auto devname = std::string_view{mDevice->mDeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, @@ -574,11 +601,11 @@ void JackPlayback::start() { if(!pnames[i]) { - ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i])); + ERR("No physical playback port for \"{}\"", jack_port_name(mPort[i])); break; } if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) - ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]), + ERR("Failed to connect output port \"{}\" to \"{}\"", jack_port_name(mPort[i]), pnames[i]); } } @@ -587,31 +614,32 @@ void JackPlayback::start() * (it won't change again after jack_activate), then allocate the ring * buffer with the appropriate size. */ - mDevice->Frequency = jack_get_sample_rate(mClient); - mDevice->UpdateSize = jack_get_buffer_size(mClient); - mDevice->BufferSize = mDevice->UpdateSize * 2; + mDevice->mSampleRate = jack_get_sample_rate(mClient); + mDevice->mUpdateSize = jack_get_buffer_size(mClient); + mDevice->mBufferSize = mDevice->mUpdateSize * 2; mRing = nullptr; if(mRTMixing) mPlaying.store(true, std::memory_order_release); else { - uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); - mDevice->BufferSize = bufsize + mDevice->UpdateSize; + uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size") + .value_or(mDevice->mUpdateSize)}; + bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); + mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); try { mPlaying.store(true, std::memory_order_release); mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this}; + 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: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } } @@ -635,12 +663,11 @@ void JackPlayback::stop() ClockLatency JackPlayback::getClockLatency() { - ClockLatency ret; - std::lock_guard dlock{mMutex}; + ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); - ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } @@ -648,7 +675,7 @@ ClockLatency JackPlayback::getClockLatency() void jack_msg_handler(const char *message) { - WARN("%s\n", message); + WARN("{}", message); } } // namespace @@ -671,9 +698,9 @@ bool JackBackendFactory::init() jack_set_error_function(old_error_cb); if(!client) { - WARN("jack_client_open() failed, 0x%02x\n", status); + WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) - ERR("Unable to connect to JACK server\n"); + ERR("Unable to connect to JACK server"); return false; } @@ -684,14 +711,11 @@ bool JackBackendFactory::init() bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string JackBackendFactory::probe(BackendType type) +auto JackBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { outnames.emplace_back(entry.mName); }; const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; @@ -705,7 +729,8 @@ std::string JackBackendFactory::probe(BackendType type) jack_client_close(client); } else - WARN("jack_client_open() failed, 0x%02x\n", status); + WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); + outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: diff --git a/libs/openal-soft/alc/backends/jack.h b/libs/openal-soft/alc/backends/jack.h index b83f24dd..1e4c9f05 100644 --- a/libs/openal-soft/alc/backends/jack.h +++ b/libs/openal-soft/alc/backends/jack.h @@ -5,15 +5,15 @@ struct JackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_JACK_H */ diff --git a/libs/openal-soft/alc/backends/loopback.cpp b/libs/openal-soft/alc/backends/loopback.cpp index e42e35b0..66b58bc2 100644 --- a/libs/openal-soft/alc/backends/loopback.cpp +++ b/libs/openal-soft/alc/backends/loopback.cpp @@ -28,7 +28,7 @@ namespace { struct LoopbackBackend final : public BackendBase { - LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } void open(std::string_view name) override; bool reset() override; @@ -39,7 +39,7 @@ struct LoopbackBackend final : public BackendBase { void LoopbackBackend::open(std::string_view name) { - mDevice->DeviceName = name; + mDeviceName = name; } bool LoopbackBackend::reset() @@ -63,8 +63,8 @@ bool LoopbackBackendFactory::init() bool LoopbackBackendFactory::querySupport(BackendType) { return true; } -std::string LoopbackBackendFactory::probe(BackendType) -{ return std::string{}; } +auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector +{ return {}; } BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } diff --git a/libs/openal-soft/alc/backends/loopback.h b/libs/openal-soft/alc/backends/loopback.h index cb42b3c8..876a052c 100644 --- a/libs/openal-soft/alc/backends/loopback.h +++ b/libs/openal-soft/alc/backends/loopback.h @@ -5,15 +5,15 @@ struct LoopbackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_LOOPBACK_H */ diff --git a/libs/openal-soft/alc/backends/null.cpp b/libs/openal-soft/alc/backends/null.cpp index afbe8056..25511d7f 100644 --- a/libs/openal-soft/alc/backends/null.cpp +++ b/libs/openal-soft/alc/backends/null.cpp @@ -27,11 +27,8 @@ #include #include #include -#include #include -#include "almalloc.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" @@ -48,7 +45,7 @@ using namespace std::string_view_literals; struct NullBackend final : public BackendBase { - NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); @@ -63,7 +60,7 @@ struct NullBackend final : public BackendBase { int NullBackend::mixerProc() { - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; + const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; SetRTPriority(); althrd_setname(GetMixerThreadName()); @@ -76,16 +73,17 @@ int NullBackend::mixerProc() auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast((now-start) * mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) + const auto avail = int64_t{std::chrono::duration_cast((now-start) + * mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } - while(avail-done >= mDevice->UpdateSize) + while(avail-done >= mDevice->mUpdateSize) { - mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); - done += mDevice->UpdateSize; + mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); + done += mDevice->mUpdateSize; } /* For every completed second, increment the start time and reduce the @@ -93,11 +91,11 @@ int NullBackend::mixerProc() * and current time from growing too large, while maintaining the * correct number of samples to render. */ - if(done >= mDevice->Frequency) + if(done >= mDevice->mSampleRate) { - seconds s{done/mDevice->Frequency}; + seconds s{done/mDevice->mSampleRate}; start += s; - done -= mDevice->Frequency*s.count(); + done -= mDevice->mSampleRate*s.count(); } } @@ -110,10 +108,10 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; - mDevice->DeviceName = name; + mDeviceName = name; } bool NullBackend::reset() @@ -126,11 +124,11 @@ void NullBackend::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; + mThread = std::thread{&NullBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -150,17 +148,17 @@ bool NullBackendFactory::init() bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string NullBackendFactory::probe(BackendType type) +auto NullBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: /* Include null char. */ - return std::string{GetDeviceName()} + '\0'; + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return std::string{}; + return {}; } BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/null.h b/libs/openal-soft/alc/backends/null.h index 7048cad6..213842af 100644 --- a/libs/openal-soft/alc/backends/null.h +++ b/libs/openal-soft/alc/backends/null.h @@ -5,15 +5,15 @@ struct NullBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_NULL_H */ diff --git a/libs/openal-soft/alc/backends/oboe.cpp b/libs/openal-soft/alc/backends/oboe.cpp index 0b9c1927..2aa9960c 100644 --- a/libs/openal-soft/alc/backends/oboe.cpp +++ b/libs/openal-soft/alc/backends/oboe.cpp @@ -24,7 +24,7 @@ using namespace std::string_view_literals; struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { - OboePlayback(DeviceBase *device) : BackendBase{device} { } + explicit OboePlayback(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -54,8 +54,9 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) { if(error == oboe::Result::ErrorDisconnected) - mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); - TRACE("Error was %s", oboe::convertToText(error)); + mDevice->handleDisconnect("Oboe AudioStream was disconnected: {}", + oboe::convertToText(error)); + TRACE("Error was {}", oboe::convertToText(error)); } void OboePlayback::open(std::string_view name) @@ -63,8 +64,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; @@ -72,10 +73,10 @@ void OboePlayback::open(std::string_view name) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->openManagedStream(stream)}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - mDevice->DeviceName = name; + mDeviceName = name; } bool OboePlayback::reset() @@ -95,7 +96,7 @@ bool OboePlayback::reset() if(mDevice->Flags.test(FrequencyRequest)) { builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); - builder.setSampleRate(static_cast(mDevice->Frequency)); + builder.setSampleRate(static_cast(mDevice->mSampleRate)); } if(mDevice->Flags.test(ChannelsRequest)) { @@ -145,11 +146,11 @@ bool OboePlayback::reset() result = builder.openManagedStream(mStream); } if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->BufferSize), + mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->mBufferSize), mStream->getBufferCapacityInFrames())); - TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); if(static_cast(mStream->getChannelCount()) != mDevice->channelsFromFmt()) { @@ -159,7 +160,7 @@ bool OboePlayback::reset() mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled channel count: %d", mStream->getChannelCount()}; + "Got unhandled channel count: {}", mStream->getChannelCount()}; } setDefaultWFXChannelOrder(); @@ -183,18 +184,18 @@ bool OboePlayback::reset() case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: throw al::backend_exception{al::backend_error::DeviceError, - "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())}; + "Got unhandled sample type: {}", oboe::convertToText(mStream->getFormat())}; } - mDevice->Frequency = static_cast(mStream->getSampleRate()); + mDevice->mSampleRate = static_cast(mStream->getSampleRate()); /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 * indicating variable updates, but OpenAL should have a reasonable minimum update size set. * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ - mDevice->UpdateSize = std::max(mDevice->Frequency/100u, + mDevice->mUpdateSize = std::max(mDevice->mSampleRate/100u, static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, + mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; @@ -204,7 +205,7 @@ void OboePlayback::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } @@ -212,12 +213,12 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); + ERR("Failed to stop stream: {}", oboe::convertToText(result)); } struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback { - OboeCapture(DeviceBase *device) : BackendBase{device} { } + explicit OboeCapture(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; @@ -246,8 +247,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) @@ -255,7 +256,7 @@ void OboeCapture::open(std::string_view name) ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) ->setChannelConversionAllowed(true) ->setFormatConversionAllowed(true) - ->setSampleRate(static_cast(mDevice->Frequency)) + ->setSampleRate(static_cast(mDevice->mSampleRate)) ->setCallback(this); /* Only use mono or stereo at user request. There's no telling what * other counts may be inferred as. @@ -273,9 +274,10 @@ void OboeCapture::open(std::string_view name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -300,28 +302,28 @@ void OboeCapture::open(std::string_view name) case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } oboe::Result result{builder.openManagedStream(mStream)}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; - TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); + TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ - mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), + mRing = RingBuffer::Create(std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u), static_cast(mStream->getBytesPerFrame()), false); - mDevice->DeviceName = name; + mDeviceName = name; } void OboeCapture::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s", + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } @@ -329,7 +331,7 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); + ERR("Failed to stop stream: {}", oboe::convertToText(result)); } uint OboeCapture::availableSamples() @@ -345,16 +347,15 @@ bool OboeBackendFactory::init() { return true; } bool OboeBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string OboeBackendFactory::probe(BackendType type) +auto OboeBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Include null char. */ - return std::string{GetDeviceName()} + '\0'; + return std::vector{std::string{GetDeviceName()}}; } - return std::string{}; + return {}; } BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/oboe.h b/libs/openal-soft/alc/backends/oboe.h index a39c2445..d277cfe7 100644 --- a/libs/openal-soft/alc/backends/oboe.h +++ b/libs/openal-soft/alc/backends/oboe.h @@ -5,15 +5,15 @@ struct OboeBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OBOE_H */ diff --git a/libs/openal-soft/alc/backends/opensl.cpp b/libs/openal-soft/alc/backends/opensl.cpp index c7056fae..155972b9 100644 --- a/libs/openal-soft/alc/backends/opensl.cpp +++ b/libs/openal-soft/alc/backends/opensl.cpp @@ -41,6 +41,7 @@ #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "dynload.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -53,6 +54,32 @@ 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 @@ -85,6 +112,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept 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; } @@ -155,12 +183,12 @@ constexpr const char *res_str(SLresult result) noexcept inline void PrintErr(SLresult res, const char *str) { if(res != SL_RESULT_SUCCESS) UNLIKELY - ERR("%s: %s\n", str, res_str(res)); + ERR("{}: {}", str, res_str(res)); } struct OpenSLPlayback final : public BackendBase { - OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -246,7 +274,7 @@ int OpenSLPlayback::mixerProc() const size_t frame_step{mDevice->channelsFromFmt()}; if(SL_RESULT_SUCCESS != result) - mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", 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)) @@ -264,7 +292,7 @@ int OpenSLPlayback::mixerProc() } if(SL_RESULT_SUCCESS != result) { - mDevice->handleDisconnect("Failed to start playback: 0x%08x", result); + mDevice->handleDisconnect("Failed to start playback: {:#08x}", result); break; } @@ -277,35 +305,35 @@ int OpenSLPlayback::mixerProc() std::unique_lock dlock{mMutex}; auto data = mRing->getWriteVector(); - mDevice->renderSamples(data.first.buf, - static_cast(data.first.len)*mDevice->UpdateSize, frame_step); - if(data.second.len > 0) - mDevice->renderSamples(data.second.buf, - static_cast(data.second.len)*mDevice->UpdateSize, frame_step); + mDevice->renderSamples(data[0].buf, + static_cast(data[0].len)*mDevice->mUpdateSize, frame_step); + if(data[1].len > 0) + mDevice->renderSamples(data[1].buf, + static_cast(data[1].len)*mDevice->mUpdateSize, frame_step); - size_t todo{data.first.len + data.second.len}; + const auto todo = size_t{data[0].len + data[1].len}; mRing->writeAdvance(todo); dlock.unlock(); for(size_t i{0};i < todo;i++) { - if(!data.first.len) + if(!data[0].len) { - data.first = data.second; - data.second.buf = nullptr; - data.second.len = 0; + data[0] = data[1]; + data[1].buf = nullptr; + data[1].len = 0; } - result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); + result = VCALL(bufferQueue,Enqueue)(data[0].buf, mDevice->mUpdateSize*mFrameSize); PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { - mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); + mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result); break; } - data.first.len--; - data.first.buf += mDevice->UpdateSize*mFrameSize; + data[0].len--; + data[0].buf += mDevice->mUpdateSize*mFrameSize; } } @@ -318,8 +346,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + 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; @@ -360,10 +388,10 @@ void OpenSLPlayback::open(std::string_view name) mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, - "Failed to initialize OpenSL device: 0x%08x", result}; + "Failed to initialize OpenSL device: {:#08x}", result}; } - mDevice->DeviceName = name; + mDeviceName = name; } bool OpenSLPlayback::reset() @@ -396,14 +424,14 @@ bool OpenSLPlayback::reset() SLDataLocator_AndroidSimpleBufferQueue loc_bufq{}; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; + loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSource audioSrc{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); - format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + 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); @@ -434,7 +462,7 @@ bool OpenSLPlayback::reset() SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); @@ -471,8 +499,8 @@ bool OpenSLPlayback::reset() } if(SL_RESULT_SUCCESS == result) { - const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; - mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true); + const uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; + mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->mUpdateSize, true); } if(SL_RESULT_SUCCESS != result) @@ -504,15 +532,15 @@ void OpenSLPlayback::start() } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to register callback: 0x%08x", result}; + "Failed to register callback: {:#08x}", result}; try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this); + mThread = std::thread(&OpenSLPlayback::mixerProc, this); } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -565,15 +593,15 @@ ClockLatency OpenSLPlayback::getClockLatency() std::lock_guard dlock{mMutex}; ret.ClockTime = mDevice->getClockTime(); - ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; - ret.Latency /= mDevice->Frequency; + ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->mUpdateSize}; + ret.Latency /= mDevice->mSampleRate; return ret; } struct OpenSLCapture final : public BackendBase { - OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; @@ -622,8 +650,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); @@ -641,16 +669,16 @@ void OpenSLCapture::open(std::string_view name) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + uint length{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, - mDevice->Frequency/100u*5u)}; + uint update_len{std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u, + mDevice->mSampleRate/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); - mDevice->UpdateSize = update_len; - mDevice->BufferSize = static_cast(mRing->writeSpace() * update_len); + mDevice->mUpdateSize = update_len; + mDevice->mBufferSize = static_cast(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { @@ -669,14 +697,14 @@ void OpenSLCapture::open(std::string_view name) SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize; + loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSink audioSnk{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); - format_pcm_ex.sampleRate = mDevice->Frequency * 1000; + 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); @@ -699,7 +727,7 @@ void OpenSLCapture::open(std::string_view name) SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); - format_pcm.samplesPerSec = mDevice->Frequency * 1000; + format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); @@ -751,20 +779,20 @@ void OpenSLCapture::open(std::string_view name) } if(SL_RESULT_SUCCESS == result) { - const uint chunk_size{mDevice->UpdateSize * mFrameSize}; + const uint chunk_size{mDevice->mUpdateSize * mFrameSize}; const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); - std::fill_n(data.first.buf, data.first.len*chunk_size, silence); - std::fill_n(data.second.buf, data.second.len*chunk_size, silence); - for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) + std::fill_n(data[0].buf, data[0].len*chunk_size, silence); + std::fill_n(data[1].buf, data[1].len*chunk_size, silence); + for(size_t i{0u};i < data[0].len && SL_RESULT_SUCCESS == result;i++) { - result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); + result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } - for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) + for(size_t i{0u};i < data[1].len && SL_RESULT_SUCCESS == result;i++) { - result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); + result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } @@ -781,10 +809,10 @@ void OpenSLCapture::open(std::string_view name) mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, - "Failed to initialize OpenSL device: 0x%08x", result}; + "Failed to initialize OpenSL device: {:#08x}", result}; } - mDevice->DeviceName = name; + mDeviceName = name; } void OpenSLCapture::start() @@ -800,7 +828,7 @@ void OpenSLCapture::start() } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start capture: 0x%08x", result}; + "Failed to start capture: {:#08x}", result}; } void OpenSLCapture::stop() @@ -818,7 +846,7 @@ void OpenSLCapture::stop() void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { - const uint update_size{mDevice->UpdateSize}; + const uint update_size{mDevice->mUpdateSize}; const uint chunk_size{update_size * mFrameSize}; /* Read the desired samples from the ring buffer then advance its read @@ -829,7 +857,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) for(uint i{0};i < samples;) { const uint rem{std::min(samples - i, update_size - mSplOffset)}; - std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, + std::copy_n(rdata[0].buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); mSplOffset += rem; @@ -839,11 +867,11 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) mSplOffset = 0; ++adv_count; - rdata.first.len -= 1; - if(!rdata.first.len) - rdata.first = rdata.second; + rdata[0].len -= 1; + if(!rdata[0].len) + rdata[0] = rdata[1]; else - rdata.first.buf += chunk_size; + rdata[0].buf += chunk_size; } i += rem; @@ -857,7 +885,7 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS != result) UNLIKELY { - mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); + mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result); bufferQueue = nullptr; } } @@ -876,20 +904,20 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) SLresult result{SL_RESULT_SUCCESS}; auto wdata = mRing->getWriteVector(); - if(adv_count > wdata.second.len) LIKELY + if(adv_count > wdata[1].len) LIKELY { - auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len); - auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1); + auto len1 = std::min(wdata[0].len, adv_count-wdata[1].len); + auto buf1 = wdata[0].buf + chunk_size*(wdata[0].len-len1); for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } - if(wdata.second.len > 0) + if(wdata[1].len > 0) { - auto len2 = std::min(wdata.second.len, adv_count); - auto buf2 = wdata.second.buf + chunk_size*(wdata.second.len-len2); + auto len2 = std::min(wdata[1].len, adv_count); + auto buf2 = wdata[1].buf + chunk_size*(wdata[1].len-len2); for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); @@ -899,25 +927,56 @@ void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) } uint OpenSLCapture::availableSamples() -{ return static_cast(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); } +{ return static_cast(mRing->readSpace()*mDevice->mUpdateSize - mSplOffset); } } // namespace -bool OSLBackendFactory::init() { return true; } +bool OSLBackendFactory::init() +{ +#if HAVE_DYNLOAD + if(!sles_handle) + { +#define SLES_LIBNAME "libOpenSLES.so" + sles_handle = LoadLib(SLES_LIBNAME); + if(!sles_handle) + { + WARN("Failed to load {}", SLES_LIBNAME); + return false; + } + + std::string missing_syms; +#define LOAD_SYMBOL(f) do { \ + p##f = reinterpret_cast(GetSymbol(sles_handle, #f)); \ + if(p##f == nullptr) missing_syms += "\n" #f; \ +} while(0) + SLES_SYMBOLS(LOAD_SYMBOL); +#undef LOAD_SYMBOL + + if(!missing_syms.empty()) + { + WARN("Missing expected symbols:{}", missing_syms); + CloseLib(sles_handle); + sles_handle = nullptr; + return false; + } + } +#endif + + return true; +} bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSLBackendFactory::probe(BackendType type) +auto OSLBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Include null char. */ - return std::string{GetDeviceName()} + '\0'; + return std::vector{std::string{GetDeviceName()}}; } - return std::string{}; + return {}; } BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/opensl.h b/libs/openal-soft/alc/backends/opensl.h index b8162447..9f13dd71 100644 --- a/libs/openal-soft/alc/backends/opensl.h +++ b/libs/openal-soft/alc/backends/opensl.h @@ -5,15 +5,15 @@ struct OSLBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSL_H */ diff --git a/libs/openal-soft/alc/backends/oss.cpp b/libs/openal-soft/alc/backends/oss.cpp index b31c13f6..cf73c18c 100644 --- a/libs/openal-soft/alc/backends/oss.cpp +++ b/libs/openal-soft/alc/backends/oss.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -43,13 +42,12 @@ #include #include "alc/alconfig.h" -#include "almalloc.h" #include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include @@ -169,18 +167,13 @@ void ALCossListAppend(std::vector &list, std::string_view handle, std::s auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); }; - int count{1}; - std::string newname{handle}; + auto count = 1; + auto newname = std::string{handle}; while(checkName(newname)) - { - newname = handle; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", handle, ++count); - const DevMap &entry = list.emplace_back(std::move(newname), path); - - TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); + 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) @@ -189,13 +182,13 @@ void ALCossListPopulate(std::vector &devlist, int type_flag) FileHandle file; if(!file.open("/dev/mixer", O_RDONLY)) { - TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str()); + 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: %s\n", std::generic_category().message(errno).c_str()); + TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno)); goto done; } @@ -205,8 +198,7 @@ void ALCossListPopulate(std::vector &devlist, int type_flag) ai.dev = i; if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, - std::generic_category().message(errno).c_str()); + ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno)); continue; } if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') @@ -256,7 +248,7 @@ uint log2i(uint x) struct OSSPlayback final : public BackendBase { - OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); @@ -302,13 +294,13 @@ int OSSPlayback::mixerProc() if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); - ERR("poll failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str()); + 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\n"); + WARN("poll timeout"); continue; } @@ -323,8 +315,8 @@ int OSSPlayback::mixerProc() if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; const auto errstr = std::generic_category().message(errno); - ERR("write failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str()); + ERR("write failed: {}", errstr); + mDevice->handleDisconnect("Failed writing playback samples: {}", errstr); break; } @@ -352,20 +344,20 @@ void OSSPlayback::open(std::string_view name) ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } - int fd{::open(devname, O_WRONLY)}; + 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 %s: %s", devname, - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, + std::generic_category().message(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; - mDevice->DeviceName = name; + mDeviceName = name; } bool OSSPlayback::reset() @@ -390,31 +382,33 @@ bool OSSPlayback::reset() break; } - uint periods{mDevice->BufferSize / mDevice->UpdateSize}; + uint periods{mDevice->mBufferSize / mDevice->mUpdateSize}; uint numChannels{mDevice->channelsFromFmt()}; - uint ossSpeed{mDevice->Frequency}; + uint ossSpeed{mDevice->mSampleRate}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)}; + uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) \ - throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \ - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \ + std::generic_category().message(errno)}; /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ + /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */ ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); + /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */ #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) { - ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans), + ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels); return false; } @@ -423,18 +417,18 @@ bool OSSPlayback::reset() (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) { - ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType), - ossFormat); + ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), + as_unsigned(ossFormat)); return false; } - mDevice->Frequency = ossSpeed; - mDevice->UpdateSize = static_cast(info.fragsize) / frameSize; - mDevice->BufferSize = static_cast(info.fragments) * mDevice->UpdateSize; + mDevice->mSampleRate = ossSpeed; + mDevice->mUpdateSize = static_cast(info.fragsize) / frameSize; + mDevice->mBufferSize = static_cast(info.fragments) * mDevice->mUpdateSize; setDefaultChannelOrder(); - mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt()); + mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt()); return true; } @@ -443,11 +437,11 @@ void OSSPlayback::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this}; + mThread = std::thread{&OSSPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -457,13 +451,13 @@ void OSSPlayback::stop() return; mThread.join(); - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); + 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 { - OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); @@ -507,25 +501,25 @@ int OSScapture::recordProc() if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); - ERR("poll failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str()); + 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\n"); + WARN("poll timeout"); continue; } auto vec = mRing->getWriteVector(); - if(vec.first.len > 0) + if(vec[0].len > 0) { - ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; + ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)}; if(amt < 0) { const auto errstr = std::generic_category().message(errno); - ERR("read failed: %s\n", errstr.c_str()); - mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str()); + ERR("read failed: {}", errstr); + mDevice->handleDisconnect("Failed reading capture samples: {}", errstr); break; } mRing->writeAdvance(static_cast(amt)/frame_size); @@ -552,14 +546,14 @@ void OSScapture::open(std::string_view name) ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_RDONLY); + mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */ if(mFd == -1) - throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, + std::generic_category().message(errno)}; int ossFormat{}; switch(mDevice->FmtType) @@ -578,55 +572,57 @@ void OSScapture::open(std::string_view name) case DevFmtUInt: case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } uint periods{4}; uint numChannels{mDevice->channelsFromFmt()}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; - uint ossSpeed{mDevice->Frequency}; + uint ossSpeed{mDevice->mSampleRate}; /* according to the OSS spec, 16 bytes are the minimum */ - uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)}; + uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ - throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ - std::generic_category().message(errno).c_str()}; \ + 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 %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans), + "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 %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType), - ossFormat}; + "Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), + as_unsigned(ossFormat)}; - mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false); + mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false); - mDevice->DeviceName = name; + mDeviceName = name; } void OSScapture::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this}; + mThread = std::thread{&OSScapture::recordProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording thread: %s", e.what()}; + "Failed to start recording thread: {}", e.what()}; } } @@ -636,8 +632,8 @@ void OSScapture::stop() return; mThread.join(); - if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); + if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ + ERR("Error resetting device: {}", std::generic_category().message(errno)); } void OSScapture::captureSamples(std::byte *buffer, uint samples) @@ -668,18 +664,13 @@ bool OSSBackendFactory::init() bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSSBackendFactory::probe(BackendType type) +auto OSSBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { - struct stat buf; - if(stat(entry.device_name.c_str(), &buf) == 0) - { - /* Includes null char. */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - } + if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0) + outnames.emplace_back(entry.name); }; switch(type) @@ -687,12 +678,14 @@ std::string OSSBackendFactory::probe(BackendType type) case BackendType::Playback: PlaybackDevices.clear(); ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/libs/openal-soft/alc/backends/oss.h b/libs/openal-soft/alc/backends/oss.h index 4f2c00b9..b5faf96a 100644 --- a/libs/openal-soft/alc/backends/oss.h +++ b/libs/openal-soft/alc/backends/oss.h @@ -5,15 +5,15 @@ struct OSSBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSS_H */ diff --git a/libs/openal-soft/alc/backends/otherio.cpp b/libs/openal-soft/alc/backends/otherio.cpp new file mode 100644 index 00000000..e7b33a78 --- /dev/null +++ b/libs/openal-soft/alc/backends/otherio.cpp @@ -0,0 +1,700 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2024 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "otherio.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WAVEFORMATEXTENSIBLE_ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "alnumeric.h" +#include "althrd_setname.h" +#include "comptr.h" +#include "core/converter.h" +#include "core/device.h" +#include "core/helpers.h" +#include "core/logging.h" +#include "strutils.h" + + +/* A custom C++ interface that should be capable of interoperating with ASIO + * drivers. + */ +enum class ORIOError : LONG { + Okay = 0, + Success = 0x3f4847a0, + NotPresent = -1000, + HWMalfunction, + InvalidParameter, + InvalidMode, + SPNotAdvancing, + NoClock, + NoMemory, +}; + +/* A 64-bit integer or double, which has the most significant 32-bit word first. */ +struct ORIO64Bit { + uint32_t hi; + uint32_t lo; + + template + auto as() const -> T = delete; +}; + +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; } +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> int64_t { return static_cast(as()); } +template<> [[nodiscard]] +auto ORIO64Bit::as() const -> double { return al::bit_cast(as()); } + + +enum class ORIOSampleType : LONG { + Int16BE = 0, + Int24BE = 1, + Int32BE = 2, + Float32BE = 3, + Float64BE = 4, + Int32BE16 = 8, + Int32BE18 = 9, + Int32BE20 = 10, + Int32BE24 = 11, + + Int16LE = 16, + Int24LE = 17, + Int32LE = 18, + Float32LE = 19, + Float64LE = 20, + Int32LE16 = 24, + Int32LE18 = 25, + Int32LE20 = 26, + Int32LE24 = 27, + + DSDInt8LSB1 = 32, + DSDInt8MSB1 = 33, + + DSDInt8 = 40, +}; + +struct ORIOClockSource { + LONG mIndex; + LONG mAssocChannel; + LONG mAssocGroup; + LONG mIsCurrent; + std::array mName; +}; + +struct ORIOChannelInfo { + LONG mChannel; + LONG mIsInput; + LONG mIsActive; + LONG mGroup; + ORIOSampleType mSampleType; + std::array mName; +}; + +struct ORIOBufferInfo { + LONG mIsInput; + LONG mChannelNum; + std::array mBuffers; +}; + +struct ORIOTime { + struct TimeInfo { + double mSpeed; + ORIO64Bit mSystemTime; + ORIO64Bit mSamplePosition; + double mSampleRate; + ULONG mFlags; + std::array mReserved; + }; + struct TimeCode { + double mSpeed; + ORIO64Bit mTimeCodeSamples; + ULONG mFlags; + std::array mFuture; + }; + + std::array mReserved; + TimeInfo mTimeInfo; + TimeCode mTimeCode; +}; + +#ifdef _WIN64 +#define ORIO_CALLBACK CALLBACK +#else +#define ORIO_CALLBACK +#endif + +struct ORIOCallbacks { + void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept; + void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept; + auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG; + auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*; +}; + +/* COM interfaces don't include a virtual destructor in their pure-virtual + * classes, and we can't add one without breaking ABI. + */ +#ifdef __GNUC__ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") +#endif +/* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */ +struct ORIOiface : public IUnknown { + STDMETHOD_(LONG, Init)(void *sysHandle) = 0; + /* A fixed-length span should be passed exactly the same as one pointer. + * This ensures an appropriately-sized buffer for the driver. + */ + STDMETHOD_(void, GetDriverName)(al::span name) = 0; + STDMETHOD_(LONG, GetDriverVersion)() = 0; + STDMETHOD_(void, GetErrorMessage)(al::span message) = 0; + STDMETHOD_(ORIOError, Start)() = 0; + STDMETHOD_(ORIOError, Stop)() = 0; + STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0; + STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0; + STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0; + STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0; + STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0; + STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0; + STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0; + STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0; + STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0; + STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0; + STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0; + STDMETHOD_(ORIOError, DisposeBuffers)() = 0; + STDMETHOD_(ORIOError, ControlPanel)() = 0; + STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0; + STDMETHOD_(ORIOError, OutputReady)() = 0; + + ORIOiface() = default; + ORIOiface(const ORIOiface&) = delete; + auto operator=(const ORIOiface&) -> ORIOiface& = delete; + ~ORIOiface() = delete; +}; +#ifdef __GNUC__ +_Pragma("GCC diagnostic pop") +#endif + +namespace { + +using namespace std::string_view_literals; +using std::chrono::nanoseconds; +using std::chrono::milliseconds; +using std::chrono::seconds; + + +struct DeviceEntry { + std::string mDrvName; + CLSID mDrvGuid{}; +}; + +std::vector gDeviceList; + + +struct KeyCloser { + void operator()(HKEY key) { RegCloseKey(key); } +}; +using KeyPtr = std::unique_ptr,KeyCloser>; + +[[nodiscard]] +auto PopulateDeviceList() -> HRESULT +{ + auto regbase = KeyPtr{}; + auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ, + al::out_ptr(regbase)); + if(res != ERROR_SUCCESS) + { + ERR("Error opening HKLM\\Software\\ASIO: {}", res); + return E_NOINTERFACE; + } + + auto numkeys = DWORD{}; + auto maxkeylen = DWORD{}; + res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr); + if(res != ERROR_SUCCESS) + { + ERR("Error querying HKLM\\Software\\ASIO info: {}", res); + return E_FAIL; + } + + /* maxkeylen is the max number of unicode characters a subkey is. A unicode + * character can occupy two WCHARs, so ensure there's enough space for them + * and the null char. + */ + auto keyname = std::vector(maxkeylen*2 + 1); + for(DWORD i{0};i < numkeys;++i) + { + auto namelen = static_cast(keyname.size()); + res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr, + nullptr); + if(res != ERROR_SUCCESS) + { + ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res); + continue; + } + if(namelen == 0) + { + ERR("HKLM\\Software\\ASIO subkey {} is blank?", i); + continue; + } + auto subkeyname = wstr_to_utf8({keyname.data(), namelen}); + + auto subkey = KeyPtr{}; + res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey)); + if(res != ERROR_SUCCESS) + { + ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res); + continue; + } + + auto idstr = std::array{}; + auto readsize = DWORD{idstr.size()*sizeof(WCHAR)}; + res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(), + &readsize); + if(res != ERROR_SUCCESS) + { + ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res); + continue; + } + idstr.back() = 0; + + auto guid = CLSID{}; + if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr)) + { + ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()), + as_unsigned(hr)); + continue; + } + + /* The CLSID is also used for the IID. */ + auto iface = ComPtr{}; + auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface)); + if(SUCCEEDED(hr)) + { +#if !ALSOFT_UWP + if(!iface->Init(GetForegroundWindow())) +#else + if(!iface->Init(nullptr)) +#endif + { + ERR("Failed to initialize {}", subkeyname); + continue; + } + auto drvname = std::array{}; + iface->GetDriverName(drvname); + auto drvver = iface->GetDriverVersion(); + + auto &entry = gDeviceList.emplace_back(); + entry.mDrvName = drvname.data(); + entry.mDrvGuid = guid; + + TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}", + entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], + guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + } + else + ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}", + subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], + guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7], as_unsigned(hr)); + } + + return S_OK; +} + + +enum class MsgType { + OpenDevice, + ResetDevice, + StartDevice, + StopDevice, + CloseDevice, + + QuitThread +}; + +constexpr const char *GetMessageTypeName(MsgType type) noexcept +{ + switch(type) + { + case MsgType::OpenDevice: return "Open Device"; + case MsgType::ResetDevice: return "Reset Device"; + case MsgType::StartDevice: return "Start Device"; + case MsgType::StopDevice: return "Stop Device"; + case MsgType::CloseDevice: return "Close Device"; + case MsgType::QuitThread: break; + } + return ""; +} + + +/* Proxy interface used by the message handler, to ensure COM objects are used + * on a thread where COM is initialized. + */ +struct OtherIOProxy { + OtherIOProxy() = default; + OtherIOProxy(const OtherIOProxy&) = delete; + OtherIOProxy(OtherIOProxy&&) = delete; + virtual ~OtherIOProxy() = default; + + void operator=(const OtherIOProxy&) = delete; + void operator=(OtherIOProxy&&) = delete; + + virtual HRESULT openProxy(std::string_view name) = 0; + virtual void closeProxy() = 0; + + virtual HRESULT resetProxy() = 0; + virtual HRESULT startProxy() = 0; + virtual void stopProxy() = 0; + + struct Msg { + MsgType mType; + OtherIOProxy *mProxy; + std::string_view mParam; + std::promise mPromise; + + explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } + }; + static inline std::deque mMsgQueue; + static inline std::mutex mMsgQueueLock; + static inline std::condition_variable mMsgQueueCond; + + auto pushMessage(MsgType type, std::string_view param={}) -> std::future + { + auto promise = std::promise{}; + auto future = std::future{promise.get_future()}; + { + auto msglock = std::lock_guard{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); + } + mMsgQueueCond.notify_one(); + return future; + } + + static auto popMessage() -> Msg + { + auto lock = std::unique_lock{mMsgQueueLock}; + mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); + auto msg = Msg{std::move(mMsgQueue.front())}; + mMsgQueue.pop_front(); + return msg; + } + + static void messageHandler(std::promise *promise); +}; + +void OtherIOProxy::messageHandler(std::promise *promise) +{ + TRACE("Starting COM message thread"); + + auto com = ComWrapper{COINIT_APARTMENTTHREADED}; + if(!com) + { + WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); + promise->set_value(com.status()); + return; + } + + auto hr = PopulateDeviceList(); + if(FAILED(hr)) + { + promise->set_value(hr); + return; + } + + promise->set_value(S_OK); + promise = nullptr; + + TRACE("Starting message loop"); + while(Msg msg{popMessage()}) + { + TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")", + GetMessageTypeName(msg.mType), static_cast(msg.mType), + static_cast(msg.mProxy), msg.mParam); + + switch(msg.mType) + { + case MsgType::OpenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + + case MsgType::ResetDevice: + hr = msg.mProxy->resetProxy(); + msg.mPromise.set_value(hr); + continue; + + case MsgType::StartDevice: + hr = msg.mProxy->startProxy(); + msg.mPromise.set_value(hr); + continue; + + case MsgType::StopDevice: + msg.mProxy->stopProxy(); + msg.mPromise.set_value(S_OK); + continue; + + case MsgType::CloseDevice: + msg.mProxy->closeProxy(); + msg.mPromise.set_value(S_OK); + continue; + + case MsgType::QuitThread: + break; + } + ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)}); + msg.mPromise.set_value(E_FAIL); + } + TRACE("Message loop finished"); +} + + +struct OtherIOPlayback final : public BackendBase, OtherIOProxy { + explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + ~OtherIOPlayback() final; + + void mixerProc(); + + void open(std::string_view name) final; + auto openProxy(std::string_view name) -> HRESULT final; + void closeProxy() final; + auto reset() -> bool final; + auto resetProxy() -> HRESULT final; + void start() final; + auto startProxy() -> HRESULT final; + void stop() final; + void stopProxy() final; + + HRESULT mOpenStatus{E_FAIL}; + + std::atomic mKillNow{true}; + std::thread mThread; +}; + +OtherIOPlayback::~OtherIOPlayback() +{ + if(SUCCEEDED(mOpenStatus)) + pushMessage(MsgType::CloseDevice).wait(); +} + +void OtherIOPlayback::mixerProc() +{ + const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; + + SetRTPriority(); + althrd_setname(GetMixerThreadName()); + + auto done = int64_t{0}; + auto start = std::chrono::steady_clock::now(); + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) + { + auto now = std::chrono::steady_clock::now(); + + /* This converts from nanoseconds to nanosamples, then to samples. */ + const auto avail = int64_t{std::chrono::duration_cast((now-start) + * mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) + { + std::this_thread::sleep_for(restTime); + continue; + } + while(avail-done >= mDevice->mUpdateSize) + { + mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); + done += mDevice->mUpdateSize; + } + + if(done >= mDevice->mSampleRate) + { + auto s = seconds{done/mDevice->mSampleRate}; + start += s; + done -= mDevice->mSampleRate*s.count(); + } + } +} + + +void OtherIOPlayback::open(std::string_view name) +{ + if(name.empty() && !gDeviceList.empty()) + name = gDeviceList[0].mDrvName; + else + { + auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(), + [name](const DeviceEntry &entry) { return entry.mDrvName == name; }); + if(iter == gDeviceList.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"{}\" not found", name}; + } + + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name}; + + mDeviceName = name; +} + +auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT +{ + return S_OK; +} + +void OtherIOPlayback::closeProxy() +{ +} + +auto OtherIOPlayback::reset() -> bool +{ + return SUCCEEDED(pushMessage(MsgType::ResetDevice).get()); +} + +auto OtherIOPlayback::resetProxy() -> HRESULT +{ + setDefaultWFXChannelOrder(); + return S_OK; +} + +void OtherIOPlayback::start() +{ + auto hr = pushMessage(MsgType::StartDevice).get(); + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start playback: {:#x}", as_unsigned(hr)}; +} + +auto OtherIOPlayback::startProxy() -> HRESULT +{ + try { + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{&OtherIOPlayback::mixerProc, this}; + return S_OK; + } + catch(std::exception& e) { + ERR("Failed to start mixing thread: {}", e.what()); + } + return E_FAIL; +} + +void OtherIOPlayback::stop() +{ + pushMessage(MsgType::StopDevice).wait(); +} + +void OtherIOPlayback::stopProxy() +{ + if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) + return; + mThread.join(); +} + +} // namespace + + +auto OtherIOBackendFactory::init() -> bool +{ + static HRESULT InitResult{E_FAIL}; + if(FAILED(InitResult)) try + { + auto promise = std::promise{}; + auto future = promise.get_future(); + + std::thread{&OtherIOProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); + } + catch(...) { + } + + return SUCCEEDED(InitResult); +} + +auto OtherIOBackendFactory::querySupport(BackendType type) -> bool +{ return type == BackendType::Playback; } + +auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector +{ + std::vector outnames; + + switch(type) + { + case BackendType::Playback: + std::for_each(gDeviceList.cbegin(), gDeviceList.cend(), + [&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); }); + break; + + case BackendType::Capture: + break; + } + + return outnames; +} + +auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr +{ + if(type == BackendType::Playback) + return BackendPtr{new OtherIOPlayback{device}}; + return nullptr; +} + +auto OtherIOBackendFactory::getFactory() -> BackendFactory& +{ + static auto factory = OtherIOBackendFactory{}; + return factory; +} + +auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport +{ + return alc::EventSupport::NoSupport; +} diff --git a/libs/openal-soft/alc/backends/otherio.h b/libs/openal-soft/alc/backends/otherio.h new file mode 100644 index 00000000..64cb436e --- /dev/null +++ b/libs/openal-soft/alc/backends/otherio.h @@ -0,0 +1,21 @@ +#ifndef BACKENDS_OTHERIO_H +#define BACKENDS_OTHERIO_H + +#include "base.h" + +struct OtherIOBackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_OTHERIO_H */ diff --git a/libs/openal-soft/alc/backends/pipewire.cpp b/libs/openal-soft/alc/backends/pipewire.cpp index d8358f7c..12951bb8 100644 --- a/libs/openal-soft/alc/backends/pipewire.cpp +++ b/libs/openal-soft/alc/backends/pipewire.cpp @@ -23,25 +23,31 @@ #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 +#include #include -#include "albit.h" #include "alc/alconfig.h" +#include "alc/backends/base.h" #include "almalloc.h" -#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "core/devformat.h" @@ -49,6 +55,8 @@ #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" +#include "fmt/ranges.h" #include "opthelpers.h" #include "ringbuffer.h" @@ -72,6 +80,7 @@ _Pragma("GCC diagnostic ignored \"-Weverything\"") #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" @@ -126,6 +135,9 @@ _Pragma("GCC diagnostic pop") namespace { +template [[nodiscard]] constexpr +auto as_const_ptr(T *ptr) noexcept -> std::add_const_t* { return ptr; } + struct PodDynamicBuilder { private: std::vector mStorage; @@ -137,7 +149,7 @@ private: mStorage.resize(size); } catch(...) { - ERR("Failed to resize POD storage\n"); + ERR("Failed to resize POD storage"); return -ENOMEM; } mPod.data = mStorage.data(); @@ -146,7 +158,7 @@ private: } public: - PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) + explicit PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize) , mPod{make_pod_builder(mStorage.data(), initSize)} { static constexpr auto callbacks{[] @@ -183,12 +195,13 @@ bool check_version(const char *version) * future. */ int major{0}, minor{0}, revision{0}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); } -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define PWIRE_FUNCS(MAGIC) \ MAGIC(pw_context_connect) \ MAGIC(pw_context_destroy) \ @@ -245,7 +258,7 @@ bool pwire_load() pwire_handle = LoadLib(pwire_library); if(!pwire_handle) { - WARN("Failed to load %s\n", pwire_library); + WARN("Failed to load {}", pwire_library); return false; } @@ -259,7 +272,7 @@ bool pwire_load() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(pwire_handle); pwire_handle = nullptr; return false; @@ -405,9 +418,10 @@ struct PwStreamDeleter { }; using PwStreamPtr = std::unique_ptr; -/* Enums for bitflags... again... *sigh* */ +/* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */ constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept { return static_cast(lhs | al::to_underlying(rhs)); } +/* NOLINTEND(*EnumCastOutOfRange) */ constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept { lhs = lhs | rhs; return lhs; } @@ -491,7 +505,7 @@ struct NodeProxy { uint32_t mId{}; - PwNodePtr mNode{}; + PwNodePtr mNode; spa_hook mListener{}; NodeProxy(uint32_t id, PwNodePtr node) @@ -526,7 +540,7 @@ struct MetadataProxy { uint32_t mId{}; - PwMetadataPtr mMetadata{}; + PwMetadataPtr mMetadata; spa_hook mListener{}; MetadataProxy(uint32_t id, PwMetadataPtr mdata) @@ -547,10 +561,10 @@ struct MetadataProxy { * to objects being added to or removed from the registry. */ struct EventManager { - ThreadMainloop mLoop{}; - PwContextPtr mContext{}; - PwCorePtr mCore{}; - PwRegistryPtr mRegistry{}; + ThreadMainloop mLoop; + PwContextPtr mContext; + PwCorePtr mCore; + PwRegistryPtr mRegistry; spa_hook mRegistryListener{}; spa_hook mCoreListener{}; @@ -579,8 +593,8 @@ struct EventManager { auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } - [[nodiscard]] inline - bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept + [[nodiscard]] + auto initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept -> bool { return mInitDone.load(m); } /** @@ -753,7 +767,7 @@ void DeviceNode::Remove(uint32_t id) { if(n.mId != id) return false; - TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + TRACE("Removing device \"{}\"", n.mDevName); if(gEventHandler.initIsDone(std::memory_order_relaxed)) { const std::string msg{"Device removed: "+n.mName}; @@ -822,7 +836,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { - WARN(" Unhandled sample rate POD type: %u\n", podType); + WARN(" Unhandled sample rate POD type: {}", podType); return; } @@ -830,13 +844,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals != 3) { - WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals); + 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: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); + TRACE(" sample rate: {}, range: {}", srates[0], srates.subspan<1>()); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); @@ -847,19 +861,13 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals == 0) { - WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Enum count: {}", nvals); return; } auto srates = get_pod_body(value, nvals); /* [0] is the default, [1...size()-1] are available selections. */ - std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}}; - for(size_t i{2};i < srates.size();++i) - { - others += ", "; - others += std::to_string(srates[i]); - } - TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str()); + TRACE(" sample rate: {}, list: {}", srates[0], srates.subspan(1)); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ @@ -879,19 +887,19 @@ void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexce { if(nvals != 1) { - WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_None count: {}", nvals); return; } auto srates = get_pod_body(value); - TRACE(" sample rate: %d\n", srates[0]); + TRACE(" sample rate: {}", srates[0]); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); return; } - WARN(" Unhandled sample rate choice type: %u\n", choiceType); + WARN(" Unhandled sample rate choice type: {}", choiceType); } void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept @@ -901,7 +909,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep if(choiceType != SPA_CHOICE_None || choiceCount != 1) { - ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } @@ -932,7 +940,7 @@ void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcep else mChannels = DevFmtMono; } - TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", + TRACE(" {} position{} for {}{}", chanmap.size(), (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } @@ -944,7 +952,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex if(choiceType != SPA_CHOICE_None || choiceCount != 1) { - ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } @@ -960,7 +968,7 @@ void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noex else if(*chancount >= 1) mChannels = DevFmtMono; } - TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", + TRACE(" {} channel{} for {}", *chancount, (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); } @@ -999,7 +1007,7 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept ntype = NodeType::Duplex; else { - TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class); + TRACE("Dropping device node {} which became type \"{}\"", info->id, media_class); DeviceNode::Remove(info->id); return; } @@ -1018,20 +1026,23 @@ void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) { - ERR("Unexpected object serial: %s\n", serial_str); + ERR("Unexpected object serial: {}", serial_str); serial_id = info->id; } } #endif - std::string name; - if(nodeName && *nodeName) name = nodeName; - else name = "PipeWire node #"+std::to_string(info->id); + auto name = std::invoke([nodeName,info]() -> std::string + { + if(nodeName && *nodeName) + return std::string{nodeName}; + return fmt::format("PipeWire node #{}", info->id); + }); const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; - TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", + TRACE("Got {} device \"{}\"{}{}{}", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); - TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); + TRACE(" \"{}\" = ID {}", name, serial_id); DeviceNode &node = DeviceNode::Add(info->id); node.mSerial = serial_id; @@ -1081,8 +1092,9 @@ void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_po DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; - TRACE("Device ID %" PRIu64 " %s format:\n", node->mSerial, - (id == SPA_PARAM_EnumFormat) ? "enumerable" : "current"); + TRACE("Device ID {} {} format{}:", node->mSerial, + (id == SPA_PARAM_EnumFormat) ? "available" : "current", + (id == SPA_PARAM_EnumFormat) ? "s" : ""); const bool force_update{id == SPA_PARAM_Format}; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) @@ -1115,14 +1127,14 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const if(!type) { - TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback"); + TRACE("Default {} device cleared", isCapture ? "capture" : "playback"); if(!isCapture) DefaultSinkDevice.clear(); else DefaultSourceDevice.clear(); return 0; } if("Spa:String:JSON"sv != type) { - ERR("Unexpected %s property type: %s\n", key, type); + ERR("Unexpected {} property type: {}", key, type); return 0; } @@ -1153,8 +1165,8 @@ auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const auto propValue = get_json_string(&std::get<1>(it)); if(!propValue) break; - TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", - propValue->c_str()); + TRACE("Got default {} device \"{}\"", isCapture ? "capture" : "playback", + *propValue); if(!isCapture && DefaultSinkDevice != *propValue) { if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) @@ -1196,28 +1208,28 @@ bool EventManager::init() mLoop = ThreadMainloop::Create("PWEventThread"); if(!mLoop) { - ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno); + 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: %d)\n", errno); + 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: %d)\n", errno); + ERR("Failed to connect PipeWire event context (errno: {})", errno); return false; } mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)}; if(!mRegistry) { - ERR("Failed to get PipeWire event registry (errno: %d)\n", errno); + ERR("Failed to get PipeWire event registry (errno: {})", errno); return false; } @@ -1234,7 +1246,7 @@ bool EventManager::init() if(int res{mLoop.start()}) { - ERR("Failed to start PipeWire event thread loop (res: %d)\n", res); + ERR("Failed to start PipeWire event thread loop (res: {})", res); return false; } @@ -1272,7 +1284,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t if(!isGood) { if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) - TRACE("Ignoring node class %s\n", media_class); + TRACE("Ignoring node class {}", media_class); return; } @@ -1281,7 +1293,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, 0))}; if(!node) { - ERR("Failed to create node proxy object (errno: %d)\n", errno); + ERR("Failed to create node proxy object (errno: {})", errno); return; } @@ -1304,13 +1316,13 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t if("default"sv != data_class) { - TRACE("Ignoring metadata \"%s\"\n", data_class); + TRACE("Ignoring metadata \"{}\"", data_class); return; } if(mDefaultMetadata) { - ERR("Duplicate default metadata\n"); + ERR("Duplicate default metadata"); return; } @@ -1318,7 +1330,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t type, version, 0))}; if(!mdata) { - ERR("Failed to create metadata proxy object (errno: %d)\n", errno); + ERR("Failed to create metadata proxy object (errno: {})", errno); return; } @@ -1375,7 +1387,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; } - info.rate = device->Frequency; + info.rate = device->mSampleRate; al::span map{}; switch(device->FmtChans) @@ -1391,6 +1403,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u 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(); @@ -1424,7 +1437,7 @@ class PipeWirePlayback final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; spa_io_rate_match *mRateMatch{}; - std::vector mChannelPtrs; + std::vector mChannelPtrs; static constexpr pw_stream_events CreateEvents() { @@ -1440,7 +1453,7 @@ class PipeWirePlayback final : public BackendBase { } public: - PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWirePlayback() final { /* Stop the mainloop so the stream can be properly destroyed. */ @@ -1484,7 +1497,7 @@ void PipeWirePlayback::outputCallback() noexcept uint length{mRateMatch ? mRateMatch->size : 0u}; #endif /* If no length is specified, use the device's update size as a fallback. */ - if(!length) UNLIKELY length = mDevice->UpdateSize; + 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 @@ -1495,7 +1508,7 @@ void PipeWirePlayback::outputCallback() noexcept for(const auto &data : datas) { length = std::min(length, data.maxsize/uint{sizeof(float)}); - *chanptr_end = static_cast(data.data); + *chanptr_end = data.data; ++chanptr_end; data.chunk->offset = 0; @@ -1552,7 +1565,7 @@ void PipeWirePlayback::open(std::string_view name) auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; targetid = match->mSerial; devname = match->mName; @@ -1560,15 +1573,15 @@ void PipeWirePlayback::open(std::string_view name) if(!mLoop) { - const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; - const std::string thread_name{"ALSoftP" + std::to_string(count)}; + const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); + const auto thread_name = fmt::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: %d)", errno}; + "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: %d)", res}; + "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) @@ -1577,14 +1590,14 @@ void PipeWirePlayback::open(std::string_view name) mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire event context (errno: %d)\n", errno}; + "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to connect PipeWire event context (errno: %d)\n", errno}; + "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); @@ -1592,9 +1605,9 @@ void PipeWirePlayback::open(std::string_view name) mTargetId = targetid; if(!devname.empty()) - mDevice->DeviceName = std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = "PipeWire Output"sv; + mDeviceName = "PipeWire Output"sv; } bool PipeWirePlayback::reset() @@ -1626,13 +1639,13 @@ bool PipeWirePlayback::reset() if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) { /* Scale the update size if the sample rate changes. */ - const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; - const double updatesize{std::round(mDevice->UpdateSize * scale)}; - const double buffersize{std::round(mDevice->BufferSize * scale)}; + const double scale{static_cast(match->mSampleRate) / mDevice->mSampleRate}; + const double updatesize{std::round(mDevice->mUpdateSize * scale)}; + const double buffersize{std::round(mDevice->mBufferSize * scale)}; - mDevice->Frequency = match->mSampleRate; - mDevice->UpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); - mDevice->BufferSize = static_cast(std::max(buffersize, 128.0)); + mDevice->mSampleRate = match->mSampleRate; + mDevice->mUpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); + mDevice->mBufferSize = static_cast(std::max(buffersize, 128.0)); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; @@ -1644,12 +1657,10 @@ bool PipeWirePlayback::reset() /* Force planar 32-bit float output for playback. This is what PipeWire * handles internally, and it's easier for us too. */ - spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; + auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)}; - static constexpr uint32_t pod_buffer_size{1024}; - PodDynamicBuilder b(pod_buffer_size); - - const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; + 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"}; @@ -1658,7 +1669,7 @@ bool PipeWirePlayback::reset() * be useful? */ auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname, PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_MEDIA_TYPE, "Audio", @@ -1668,11 +1679,11 @@ bool PipeWirePlayback::reset() nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire stream properties (errno: %d)", errno}; + "Failed to create PipeWire stream properties (errno: {})", errno}; - pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, - mDevice->Frequency); - pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->mUpdateSize, + mDevice->mSampleRate); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else @@ -1684,17 +1695,17 @@ bool PipeWirePlayback::reset() 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: %d)", errno}; + "Failed to create PipeWire stream (errno: {})", errno}; static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS}; - if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false)) + if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false)) flags |= PW_STREAM_FLAG_RT_PROCESS; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream (res: %d)", res}; + "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() @@ -1703,12 +1714,12 @@ bool PipeWirePlayback::reset() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream: \"%s\"", error}; + "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); - /* TODO: Update mDevice->UpdateSize with the stream's quantum, and - * mDevice->BufferSize with the total known buffering delay from the head + /* 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. @@ -1727,7 +1738,7 @@ void PipeWirePlayback::start() MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire stream (res: %d)", res}; + "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). @@ -1738,7 +1749,7 @@ void PipeWirePlayback::start() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; + "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); @@ -1753,7 +1764,7 @@ void PipeWirePlayback::start() pw_time ptime{}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) { - ERR("Failed to get PipeWire stream time (res: %d)\n", res); + ERR("Failed to get PipeWire stream time (res: {})", res); break; } @@ -1768,11 +1779,11 @@ void PipeWirePlayback::start() const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers}; /* Ensure the delay is in sample frames. */ - const uint64_t delay{static_cast(ptime.delay) * mDevice->Frequency * + const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; - mDevice->UpdateSize = updatesize; - mDevice->BufferSize = static_cast(ptime.buffered + delay + + mDevice->mUpdateSize = updatesize; + mDevice->mBufferSize = static_cast(ptime.buffered + delay + uint64_t{totalbuffers}*updatesize); break; } @@ -1783,17 +1794,17 @@ void PipeWirePlayback::start() if(ptime.rate.denom > 0 && updatesize > 0) { /* Ensure the delay is in sample frames. */ - const uint64_t delay{static_cast(ptime.delay) * mDevice->Frequency * + const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; - mDevice->UpdateSize = updatesize; - mDevice->BufferSize = static_cast(delay + updatesize); + mDevice->mUpdateSize = updatesize; + mDevice->mBufferSize = static_cast(delay + updatesize); break; } #endif if(!--wait_count) { - ERR("Timeout getting PipeWire stream buffering info\n"); + ERR("Timeout getting PipeWire stream buffering info"); break; } @@ -1807,7 +1818,7 @@ void PipeWirePlayback::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - ERR("Failed to stop PipeWire stream (res: %d)\n", res); + ERR("Failed to stop PipeWire stream (res: {})", res); /* Wait for the stream to stop playing. */ plock.wait([stream=mStream.get()]() @@ -1828,7 +1839,7 @@ ClockLatency PipeWirePlayback::getClockLatency() { MainloopLockGuard looplock{mLoop}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) - ERR("Failed to get PipeWire stream time (res: %d)\n", res); + ERR("Failed to get PipeWire stream time (res: {})", res); } /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the @@ -1856,7 +1867,7 @@ ClockLatency PipeWirePlayback::getClockLatency() */ ptime.now = monoclock.count(); curtic = mixtime; - delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency; + delay = nanoseconds{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; } else { @@ -1916,7 +1927,7 @@ class PipeWireCapture final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; - RingBufferPtr mRing{}; + RingBufferPtr mRing; static constexpr pw_stream_events CreateEvents() { @@ -1930,7 +1941,7 @@ class PipeWireCapture final : public BackendBase { } public: - PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; @@ -2017,7 +2028,7 @@ void PipeWireCapture::open(std::string_view name) } if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + "Device name \"{}\" not found", name}; targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; @@ -2026,15 +2037,15 @@ void PipeWireCapture::open(std::string_view name) if(!mLoop) { - const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; - const std::string thread_name{"ALSoftC" + std::to_string(count)}; + const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); + const auto thread_name = fmt::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: %d)", errno}; + "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: %d)", res}; + "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) @@ -2043,14 +2054,14 @@ void PipeWireCapture::open(std::string_view name) mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire event context (errno: %d)\n", errno}; + "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to connect PipeWire event context (errno: %d)\n", errno}; + "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); @@ -2058,9 +2069,9 @@ void PipeWireCapture::open(std::string_view name) mTargetId = targetid; if(!devname.empty()) - mDevice->DeviceName = std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = "PipeWire Input"sv; + mDeviceName = "PipeWire Input"sv; bool is51rear{false}; @@ -2075,19 +2086,16 @@ void PipeWireCapture::open(std::string_view name) if(match != devlist.cend()) is51rear = match->mIs51Rear; } - spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; + auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)}; - static constexpr uint32_t pod_buffer_size{1024}; - PodDynamicBuilder b(pod_buffer_size); - - std::array params{static_cast(spa_format_audio_raw_build(b.get(), - SPA_PARAM_EnumFormat, &info))}; - if(!params[0]) + auto b = PodDynamicBuilder{}; + auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)); + if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new( PW_KEY_NODE_NAME, appname, PW_KEY_NODE_DESCRIPTION, appname, @@ -2098,15 +2106,15 @@ void PipeWireCapture::open(std::string_view name) nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to create PipeWire stream properties (errno: %d)", errno}; + "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->Frequency+25) / 50, - mDevice->Frequency); - pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->mSampleRate+25) / 50, + mDevice->mSampleRate); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else @@ -2117,15 +2125,15 @@ void PipeWireCapture::open(std::string_view name) 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: %d)", errno}; + "Failed to create PipeWire stream (errno: {})", errno}; static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; - if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)}) + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream (res: %d)", res}; + "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() @@ -2134,7 +2142,7 @@ void PipeWireCapture::open(std::string_view name) pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "Error connecting PipeWire stream: \"%s\"", error}; + "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); plock.unlock(); @@ -2142,7 +2150,7 @@ void PipeWireCapture::open(std::string_view name) setDefaultWFXChannelOrder(); /* Ensure at least a 100ms capture buffer. */ - mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize), + mRing = RingBuffer::Create(std::max(mDevice->mSampleRate/10u, mDevice->mBufferSize), mDevice->frameSizeFromFmt(), false); } @@ -2152,7 +2160,7 @@ void PipeWireCapture::start() MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start PipeWire stream (res: %d)", res}; + "Failed to start PipeWire stream (res: {})", res}; plock.wait([stream=mStream.get()]() { @@ -2160,7 +2168,7 @@ void PipeWireCapture::start() pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; + "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); } @@ -2169,7 +2177,7 @@ void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - ERR("Failed to stop PipeWire stream (res: %d)\n", res); + ERR("Failed to stop PipeWire stream (res: {})", res); plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); @@ -2192,11 +2200,11 @@ bool PipeWireBackendFactory::init() const char *version{pw_get_library_version()}; if(!check_version(version)) { - WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version, + WARN("PipeWire version \"{}\" too old ({} or newer required)", version, pw_get_headers_version()); return false; } - TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); + TRACE("Found PipeWire version \"{}\" ({} or newer)", version, pw_get_headers_version()); pw_init(nullptr, nullptr); if(!gEventHandler.init()) @@ -2209,7 +2217,7 @@ bool PipeWireBackendFactory::init() /* 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.\n"); + WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong."); return false; } return true; @@ -2218,9 +2226,9 @@ bool PipeWireBackendFactory::init() bool PipeWireBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PipeWireBackendFactory::probe(BackendType type) +auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; gEventHandler.waitForInit(); EventWatcherLockGuard evtlock{gEventHandler}; @@ -2241,31 +2249,31 @@ std::string PipeWireBackendFactory::probe(BackendType type) case BackendType::Playback: defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); if(defmatch != devlist.cend()) - { - /* Includes null char. */ - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); - } + outnames.emplace_back(defmatch->mName); for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch && iter->mType != NodeType::Source) - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(iter->mName); } break; case BackendType::Capture: + outnames.reserve(devlist.size()); defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); if(defmatch != devlist.cend()) { if(defmatch->mType == NodeType::Sink) - outnames.append(GetMonitorPrefix()); - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName); + else + outnames.emplace_back(defmatch->mName); } for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch) { if(iter->mType == NodeType::Sink) - outnames.append(GetMonitorPrefix()); - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName); + else + outnames.emplace_back(iter->mName); } } break; @@ -2274,6 +2282,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) return outnames; } + BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) diff --git a/libs/openal-soft/alc/backends/pipewire.h b/libs/openal-soft/alc/backends/pipewire.h index 5493684f..567d18ed 100644 --- a/libs/openal-soft/alc/backends/pipewire.h +++ b/libs/openal-soft/alc/backends/pipewire.h @@ -2,24 +2,26 @@ #define BACKENDS_PIPEWIRE_H #include +#include +#include "alc/events.h" #include "base.h" struct DeviceBase; struct PipeWireBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PIPEWIRE_H */ diff --git a/libs/openal-soft/alc/backends/portaudio.cpp b/libs/openal-soft/alc/backends/portaudio.cpp index 0a646713..75497c3f 100644 --- a/libs/openal-soft/alc/backends/portaudio.cpp +++ b/libs/openal-soft/alc/backends/portaudio.cpp @@ -20,32 +20,25 @@ #include "config.h" -#include "portaudio.h" +#include "portaudio.hpp" +#include #include #include #include -#include "albit.h" #include "alc/alconfig.h" -#include "alnumeric.h" -#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include /* NOLINT(*-duplicate-include) Not the same header. */ +#include namespace { -using namespace std::string_view_literals; - -[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "PortAudio Default"sv; } - - -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD void *pa_handle; #define MAKE_FUNC(x) decltype(x) * p##x MAKE_FUNC(Pa_Initialize); @@ -55,6 +48,8 @@ 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); @@ -68,35 +63,72 @@ MAKE_FUNC(Pa_GetStreamInfo); #define Pa_StopStream pPa_StopStream #define Pa_OpenStream pPa_OpenStream #define Pa_CloseStream pPa_CloseStream +#define Pa_GetDeviceCount pPa_GetDeviceCount +#define Pa_GetDeviceInfo pPa_GetDeviceInfo #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice #define Pa_GetStreamInfo pPa_GetStreamInfo #endif #endif +struct DeviceEntry { + std::string mName; + uint mPlaybackChannels{}; + uint mCaptureChannels{}; +}; +std::vector DeviceNames; + +void EnumerateDevices() +{ + const auto devcount = Pa_GetDeviceCount(); + if(devcount < 0) + { + ERR("Error getting device count: {}", Pa_GetErrorText(devcount)); + return; + } + + std::vector(static_cast(devcount)).swap(DeviceNames); + PaDeviceIndex idx{0}; + for(auto &entry : DeviceNames) + { + if(auto info = Pa_GetDeviceInfo(idx); info && info->name) + { + entry.mName = info->name; + entry.mPlaybackChannels = static_cast(std::max(info->maxOutputChannels, 0)); + entry.mCaptureChannels = static_cast(std::max(info->maxInputChannels, 0)); + TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName, + info->maxOutputChannels, info->maxInputChannels); + } + ++idx; + } +} + +struct StreamParamsExt : public PaStreamParameters { uint updateSize; }; struct PortPlayback final : public BackendBase { - PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PortPlayback() override; int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; + void createStream(PaDeviceIndex deviceid); + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; PaStream *mStream{nullptr}; - PaStreamParameters mParams{}; - uint mUpdateSize{0u}; + StreamParamsExt mParams{}; + PaDeviceIndex mDeviceIdx{-1}; }; PortPlayback::~PortPlayback() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } @@ -110,45 +142,29 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } -void PortPlayback::open(std::string_view name) +void PortPlayback::createStream(PaDeviceIndex deviceid) { - if(name.empty()) - name = GetDefaultName(); - else if(name != GetDefaultName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + auto &devinfo = DeviceNames.at(static_cast(deviceid)); - PaStreamParameters params{}; - auto devidopt = ConfigValueInt({}, "port", "device"); - if(devidopt && *devidopt >= 0) params.device = *devidopt; - else params.device = Pa_GetDefaultOutputDevice(); - params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); + auto params = StreamParamsExt{}; + params.device = deviceid; + params.suggestedLatency = mDevice->mBufferSize / static_cast(mDevice->mSampleRate); params.hostApiSpecificStreamInfo = nullptr; - - params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - + params.channelCount = static_cast(std::min(devinfo.mPlaybackChannels, + mDevice->channelsFromFmt())); switch(mDevice->FmtType) { - case DevFmtByte: - params.sampleFormat = paInt8; - break; - case DevFmtUByte: - params.sampleFormat = paUInt8; - break; - case DevFmtUShort: - /* fall-through */ - case DevFmtShort: - params.sampleFormat = paInt16; - break; - case DevFmtUInt: - /* fall-through */ - case DevFmtInt: - params.sampleFormat = paInt32; - break; - case DevFmtFloat: - params.sampleFormat = paFloat32; - break; + case DevFmtByte: params.sampleFormat = paInt8; break; + case DevFmtUByte: params.sampleFormat = paUInt8; break; + case DevFmtUShort: [[fallthrough]]; + case DevFmtShort: params.sampleFormat = paInt16; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: params.sampleFormat = paInt32; break; + case DevFmtFloat: params.sampleFormat = paFloat32; break; } + params.updateSize = mDevice->mUpdateSize; + + auto srate = uint{mDevice->mSampleRate}; static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, @@ -157,55 +173,103 @@ void PortPlayback::open(std::string_view name) return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; - PaStream *stream{}; - while(PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, - mDevice->UpdateSize, paNoFlag, writeCallback, this)}) + while(PaError err{Pa_OpenStream(&mStream, nullptr, ¶ms, srate, params.updateSize, paNoFlag, + writeCallback, this)}) { - if(params.sampleFormat != paFloat32) - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + if(params.updateSize != DefaultUpdateSize) + params.updateSize = DefaultUpdateSize; + else if(srate != 48000u) + srate = (srate != 44100u) ? 44100u : 48000u; + else if(params.sampleFormat != paInt16) + params.sampleFormat = paInt16; + else if(params.channelCount != 2) + params.channelCount = 2; + else + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", Pa_GetErrorText(err)}; - params.sampleFormat = paInt16; } - Pa_CloseStream(mStream); - mStream = stream; mParams = params; - mUpdateSize = mDevice->UpdateSize; +} - mDevice->DeviceName = name; +void PortPlayback::open(std::string_view name) +{ + if(DeviceNames.empty()) + EnumerateDevices(); + + PaDeviceIndex deviceid{-1}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "device")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultOutputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) + { return entry.mPlaybackChannels > 0 && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"{}\" not found", name}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } + + createStream(deviceid); + mDeviceIdx = deviceid; + + mDeviceName = name; } bool PortPlayback::reset() { + if(mStream) + { + auto err = Pa_CloseStream(mStream); + if(err != paNoError) + ERR("Error closing stream: {}", Pa_GetErrorText(err)); + mStream = nullptr; + } + + createStream(mDeviceIdx); + + switch(mParams.sampleFormat) + { + case paFloat32: mDevice->FmtType = DevFmtFloat; break; + case paInt32: mDevice->FmtType = DevFmtInt; break; + case paInt16: mDevice->FmtType = DevFmtShort; break; + case paInt8: mDevice->FmtType = DevFmtByte; break; + case paUInt8: mDevice->FmtType = DevFmtUByte; break; + default: + ERR("Unexpected PortAudio sample format: {}", mParams.sampleFormat); + throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: {}", + mParams.sampleFormat}; + } + + if(mParams.channelCount != static_cast(mDevice->channelsFromFmt())) + { + if(mParams.channelCount >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(mParams.channelCount == 1) + mDevice->FmtChans = DevFmtMono; + mDevice->mAmbiOrder = 0; + } + const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; - mDevice->Frequency = static_cast(streamInfo->sampleRate); - mDevice->UpdateSize = mUpdateSize; - - if(mParams.sampleFormat == paInt8) - mDevice->FmtType = DevFmtByte; - else if(mParams.sampleFormat == paUInt8) - mDevice->FmtType = DevFmtUByte; - else if(mParams.sampleFormat == paInt16) - mDevice->FmtType = DevFmtShort; - else if(mParams.sampleFormat == paInt32) - mDevice->FmtType = DevFmtInt; - else if(mParams.sampleFormat == paFloat32) - mDevice->FmtType = DevFmtFloat; - else + mDevice->mSampleRate = static_cast(std::lround(streamInfo->sampleRate)); + mDevice->mUpdateSize = mParams.updateSize; + mDevice->mBufferSize = mDevice->mUpdateSize * 2; + if(streamInfo->outputLatency > 0.0f) { - ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat); - return false; + const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; + TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency, + sampleLatency); + mDevice->mBufferSize = static_cast(std::clamp(sampleLatency, + double(mDevice->mBufferSize), double{std::numeric_limits::max()})); } - if(mParams.channelCount >= 2) - mDevice->FmtChans = DevFmtStereo; - else if(mParams.channelCount == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unexpected channel count: %u\n", mParams.channelCount); - return false; - } setDefaultChannelOrder(); return true; @@ -213,22 +277,20 @@ bool PortPlayback::reset() void PortPlayback::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) + throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}", Pa_GetErrorText(err)}; } void PortPlayback::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) + ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } struct PortCapture final : public BackendBase { - PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, @@ -250,7 +312,7 @@ PortCapture::~PortCapture() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) - ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } @@ -265,20 +327,35 @@ int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long fram void PortCapture::open(std::string_view name) { - if(name.empty()) - name = GetDefaultName(); - else if(name != GetDefaultName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + if(DeviceNames.empty()) + EnumerateDevices(); - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + int deviceid{}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "capture")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultInputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) + { return entry.mCaptureChannels > 0 && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"{}\" not found", name}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } + + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); - auto devidopt = ConfigValueInt({}, "port", "capture"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); + mParams.device = deviceid; mParams.suggestedLatency = 0.0f; mParams.hostApiSpecificStreamInfo = nullptr; @@ -301,7 +378,7 @@ void PortCapture::open(std::string_view name) break; case DevFmtUInt: case DevFmtUShort: - throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mParams.channelCount = static_cast(mDevice->channelsFromFmt()); @@ -313,29 +390,27 @@ void PortCapture::open(std::string_view name) return static_cast(userData)->readCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; - PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, + PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->mSampleRate, paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; if(err != paNoError) - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", Pa_GetErrorText(err)}; - mDevice->DeviceName = name; + mDeviceName = name; } void PortCapture::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording: %s", Pa_GetErrorText(err)}; + "Failed to start recording: {}", Pa_GetErrorText(err)}; } void PortCapture::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) - ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) + ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } @@ -350,7 +425,7 @@ void PortCapture::captureSamples(std::byte *buffer, uint samples) bool PortBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!pa_handle) { #ifdef _WIN32 @@ -383,6 +458,8 @@ bool PortBackendFactory::init() 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); @@ -391,7 +468,7 @@ bool PortBackendFactory::init() const PaError err{Pa_Initialize()}; if(err != paNoError) { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); CloseLib(pa_handle); pa_handle = nullptr; return false; @@ -401,7 +478,7 @@ bool PortBackendFactory::init() const PaError err{Pa_Initialize()}; if(err != paNoError) { - ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); return false; } #endif @@ -411,16 +488,52 @@ bool PortBackendFactory::init() bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string PortBackendFactory::probe(BackendType type) +auto PortBackendFactory::enumerate(BackendType type) -> std::vector { + std::vector devices; + + EnumerateDevices(); + int defaultid{-1}; switch(type) { case BackendType::Playback: + defaultid = Pa_GetDefaultOutputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mPlaybackChannels > 0) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } + break; + case BackendType::Capture: - /* Include null char. */ - return std::string{GetDefaultName()} + '\0'; + defaultid = Pa_GetDefaultInputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mCaptureChannels > 0) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } + break; } - return std::string{}; + + return devices; } BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/portaudio.h b/libs/openal-soft/alc/backends/portaudio.h deleted file mode 100644 index c35ccff2..00000000 --- a/libs/openal-soft/alc/backends/portaudio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_PORTAUDIO_H -#define BACKENDS_PORTAUDIO_H - -#include "base.h" - -struct PortBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - std::string probe(BackendType type) override; - - BackendPtr createBackend(DeviceBase *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_PORTAUDIO_H */ diff --git a/libs/openal-soft/alc/backends/portaudio.hpp b/libs/openal-soft/alc/backends/portaudio.hpp new file mode 100644 index 00000000..69c78ea9 --- /dev/null +++ b/libs/openal-soft/alc/backends/portaudio.hpp @@ -0,0 +1,19 @@ +#ifndef BACKENDS_PORTAUDIO_HPP +#define BACKENDS_PORTAUDIO_HPP + +#include "base.h" + +struct PortBackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_PORTAUDIO_HPP */ diff --git a/libs/openal-soft/alc/backends/pulseaudio.cpp b/libs/openal-soft/alc/backends/pulseaudio.cpp index b3e0a1bb..aac01415 100644 --- a/libs/openal-soft/alc/backends/pulseaudio.cpp +++ b/libs/openal-soft/alc/backends/pulseaudio.cpp @@ -28,27 +28,28 @@ #include #include #include +#include +#include #include -#include #include #include #include #include #include +#include #include #include #include -#include "albit.h" #include "alc/alconfig.h" -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" -#include "alstring.h" +#include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" +#include "fmt/core.h" #include "opthelpers.h" #include "strutils.h" @@ -57,9 +58,10 @@ namespace { +using namespace std::string_view_literals; using uint = unsigned int; -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ @@ -71,8 +73,10 @@ using uint = unsigned int; 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); \ @@ -144,8 +148,10 @@ PULSE_FUNCS(MAKE_FUNC) #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 @@ -251,7 +257,7 @@ constexpr pa_channel_map MonoChanMap{ }; -/* *grumble* Don't use enums for bitflags. */ +/* NOLINTBEGIN(*EnumCastOutOfRange) *grumble* Don't use enums for bitflags. */ constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) { return pa_stream_flags_t(lhs | al::to_underlying(rhs)); } constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) @@ -277,11 +283,13 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) { return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } +/* NOLINTEND(*EnumCastOutOfRange) */ struct DevMap { std::string name; std::string device_name; + uint32_t index{}; }; bool checkName(const al::span list, const std::string &name) @@ -293,6 +301,9 @@ bool checkName(const al::span list, const std::string &name) std::vector PlaybackDevices; std::vector CaptureDevices; +std::string DefaultPlaybackDevName; +std::string DefaultCaptureDevName; + /* Global flags and properties */ pa_context_flags_t pulse_ctx_flags; @@ -343,6 +354,32 @@ public: void close(pa_stream *stream=nullptr); + void updateDefaultDevice(pa_context*, const pa_server_info *info) const + { + auto default_sink = info->default_sink_name ? std::string_view{info->default_sink_name} + : std::string_view{}; + auto default_src = info->default_source_name ? std::string_view{info->default_source_name} + : std::string_view{}; + + if(default_sink != DefaultPlaybackDevName) + { + TRACE("Default playback device: {}", default_sink); + DefaultPlaybackDevName = default_sink; + + const auto msg = fmt::format("Default playback device changed: {}", default_sink); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); + } + if(default_src != DefaultCaptureDevName) + { + TRACE("Default capture device: {}", default_src); + DefaultCaptureDevName = default_src; + + const auto msg = fmt::format("Default capture device changed: {}", default_src); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); + } + signal(); + } + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept { if(eol) @@ -360,18 +397,18 @@ public: /* Make sure the display name (description) is unique. Append a number * counter as needed. */ - int count{1}; - std::string newname{info->description}; + auto count = 1; + auto newname = std::string{info->description}; while(checkName(PlaybackDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = PlaybackDevices.back(); + newname = fmt::format("{} #{}", info->description, ++count); - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname), + info->name, info->index}); + TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, + newentry.index); + + const auto msg = fmt::format("Device added: {}", newentry.device_name); + alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg); } void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept @@ -391,22 +428,77 @@ public: /* Make sure the display name (description) is unique. Append a number * counter as needed. */ - int count{1}; - std::string newname{info->description}; + auto count = 1; + auto newname = std::string{info->description}; while(checkName(CaptureDevices, newname)) - { - newname = info->description; - newname += " #"; - newname += std::to_string(++count); - } - CaptureDevices.emplace_back(DevMap{std::move(newname), info->name}); - DevMap &newentry = CaptureDevices.back(); + newname = fmt::format("{} #{}", info->description, ++count); - TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); + const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name, + info->index}); + TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, + newentry.index); + + const auto msg = fmt::format("Device added: {}", newentry.device_name); + alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg); } - void probePlaybackDevices(); - void probeCaptureDevices(); + void eventCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx) noexcept + { + const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + + if(eventFacility == PA_SUBSCRIPTION_EVENT_SERVER + && eventType == PA_SUBSCRIPTION_EVENT_CHANGE) + { + static constexpr auto server_cb = [](pa_context *ctx, const pa_server_info *info, + void *pdata) noexcept + { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; + auto *op = pa_context_get_server_info(context, server_cb, this); + if(op) pa_operation_unref(op); + } + + if(eventFacility != PA_SUBSCRIPTION_EVENT_SINK + && eventFacility != PA_SUBSCRIPTION_EVENT_SOURCE) + return; + + const auto devtype = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? alc::DeviceType::Playback : alc::DeviceType::Capture; + + if(eventType == PA_SUBSCRIPTION_EVENT_NEW) + { + if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + { + static constexpr auto devcallback = [](pa_context *ctx, const pa_sink_info *info, + int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; + auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this); + if(op) pa_operation_unref(op); + } + else + { + static constexpr auto devcallback = [](pa_context *ctx, const pa_source_info *info, + int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSourceCallback(ctx,info,eol); }; + auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this); + if(op) pa_operation_unref(op); + } + } + else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) + { + auto find_index = [idx](const DevMap &entry) noexcept { return entry.index == idx; }; + + auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? PlaybackDevices : CaptureDevices; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), find_index); + if(iter != devlist.cend()) + { + devlist.erase(iter); + + const auto msg = fmt::format("Device removed: {}", idx); + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + } + } friend struct MainloopUniqueLock; }; @@ -433,36 +525,38 @@ struct MainloopUniqueLock : public std::unique_lock { void setEventHandler() { - pa_operation *op{pa_context_subscribe(mutex()->mContext, - PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, - [](pa_context*, int, void *pdata) noexcept - { static_cast(pdata)->signal(); }, - mutex())}; + 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); - /* Watch for device added/removed events. - * - * TODO: Also track the "default" device, in as much as PulseAudio has - * the concept of a default device (whatever device is opened when not - * specifying a specific sink or source name). There doesn't seem to be - * an event for this. - */ - auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept - { - const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); - if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK - || eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE) - { - const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) - ? alc::DeviceType::Playback : alc::DeviceType::Capture; - const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); - if(eventType == PA_SUBSCRIPTION_EVENT_NEW) - alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); - else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) - alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); - } - }; - pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr); + static constexpr auto handler = [](pa_context *ctx, pa_subscription_event_type_t t, + uint32_t index, void *pdata) noexcept + { return static_cast(pdata)->eventCallback(ctx, t, index); }; + pa_context_set_subscribe_callback(context, handler, mutex()); + + /* Fill in the initial device lists, and get the defaults. */ + auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; + + auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept + { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; + + auto server_callback = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept + { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; + + auto *sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex()); + auto *srcop = pa_context_get_source_info_list(context, src_callback, mutex()); + auto *serverop = pa_context_get_server_info(context, server_callback, mutex()); + + waitForOperation(sinkop); + waitForOperation(srcop); + waitForOperation(serverop); } @@ -483,6 +577,13 @@ struct MainloopUniqueLock : public std::unique_lock { void connectContext(); pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); + + pa_stream *connectStream(const std::string &device_name, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) + { + return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr, + spec, chanmap, type); + } }; using MainloopLockGuard = std::lock_guard; @@ -532,7 +633,7 @@ void MainloopUniqueLock::connectContext() { pa_context_unref(mutex()->mContext); mutex()->mContext = nullptr; - throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect ({})", pa_strerror(err)}; } } @@ -543,7 +644,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) - throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", + 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 @@ -555,7 +656,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ if(err < 0) { pa_stream_unref(stream); - throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)", + throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect ({})", stream_id, pa_strerror(err)}; } @@ -567,7 +668,7 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_ err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, - "%s did not get ready (%s)", stream_id, pa_strerror(err)}; + "{} did not get ready ({})", stream_id, pa_strerror(err)}; } return state == PA_STREAM_READY; }); @@ -592,52 +693,12 @@ void PulseMainloop::close(pa_stream *stream) } -void PulseMainloop::probePlaybackDevices() -{ - PlaybackDevices.clear(); - try { - MainloopUniqueLock plock{*this}; - auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept - { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; - - pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)}; - plock.waitForOperation(op); - - op = pa_context_get_sink_info_list(mContext, sink_callback, this); - plock.waitForOperation(op); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - -void PulseMainloop::probeCaptureDevices() -{ - CaptureDevices.clear(); - try { - MainloopUniqueLock plock{*this}; - auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept - { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; - - pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback, - this)}; - plock.waitForOperation(op); - - op = pa_context_get_source_info_list(mContext, src_callback, this); - plock.waitForOperation(op); - } - catch(std::exception &e) { - ERR("Error enumerating devices: %s\n", e.what()); - } -} - - /* Used for initial connection test and enumeration. */ PulseMainloop gGlobalMainloop; struct PulsePlayback final : public BackendBase { - PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; @@ -655,7 +716,7 @@ struct PulsePlayback final : public BackendBase { PulseMainloop mMainloop; - std::optional mDeviceName{std::nullopt}; + std::optional mDeviceId{std::nullopt}; bool mIs51Rear{false}; pa_buffer_attr mAttr{}; @@ -678,14 +739,14 @@ void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept * leaving it alone means ALC_REFRESH will be off. */ mAttr = *(pa_stream_get_buffer_attr(stream)); - TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf); + TRACE("minreq={}, tlength={}, prebuf={}", mAttr.minreq, mAttr.tlength, mAttr.prebuf); } void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { - ERR("Received stream failure!\n"); + ERR("Received stream failure!"); mDevice->handleDisconnect("Playback stream failure"); } mMainloop.signal(); @@ -711,7 +772,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; if(ret != PA_OK) UNLIKELY - ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); + ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret)); } while(nbytes > 0); } @@ -754,11 +815,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int mIs51Rear = false; std::array chanmap_str{}; pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); - WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data()); + WARN("Failed to find format for channel map:\n {}", chanmap_str.data()); } if(info->active_port) - TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description); + TRACE("Active port: {} ({})", info->active_port->name, info->active_port->description); mDevice->Flags.set(DirectEar, (info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0)); } @@ -770,13 +831,13 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int mMainloop.signal(); return; } - mDevice->DeviceName = info->description; + mDeviceName = info->description; } void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept { - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName->c_str()); + mDeviceId = pa_stream_get_device_name(stream); + TRACE("Stream moved to {}", *mDeviceId); } @@ -787,21 +848,20 @@ void PulsePlayback::open(std::string_view name) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start device mainloop"}; - const char *pulse_name{nullptr}; - std::string_view display_name; + auto pulse_name = std::string{}; if(!name.empty()) { - if(PlaybackDevices.empty()) - mMainloop.probePlaybackDevices(); - auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; + + auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; - pulse_name = iter->device_name.c_str(); - display_name = iter->name; + "Device name \"{}\" not found", name}; + + pulse_name = iter->device_name; + mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; @@ -817,37 +877,38 @@ void PulsePlayback::open(std::string_view name) spec.rate = 44100; spec.channels = 2; - if(!pulse_name) + if(pulse_name.empty()) { static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT"); - if(defname) pulse_name = defname->c_str(); + if(defname) pulse_name = *defname; } - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); + TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + static constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); - if(pulse_name) mDeviceName.emplace(pulse_name); - else mDeviceName.reset(); - if(display_name.empty()) + if(!pulse_name.empty()) + mDeviceId.emplace(std::move(pulse_name)); + + if(mDeviceName.empty()) { - auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + static constexpr auto name_callback = [](pa_context *context, const pa_sink_info *info, + int eol, void *pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } - else - mDevice->DeviceName = display_name; } bool PulsePlayback::reset() { MainloopUniqueLock plock{mMainloop}; - const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; + const auto deviceName = mDeviceId ? mDeviceId->c_str() : nullptr; if(mStream) { @@ -860,17 +921,17 @@ bool PulsePlayback::reset() mStream = nullptr; } - auto info_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + auto info_cb = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkInfoCallback(context, info, eol); }; - pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, - info_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb, + this)}; plock.waitForOperation(op); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false)) + 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, @@ -879,7 +940,7 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) + if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -908,6 +969,9 @@ bool PulsePlayback::reset() case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + /*fall-through*/ case DevFmtX714: chanmap = X714ChanMap; break; @@ -938,38 +1002,41 @@ bool PulsePlayback::reset() mSpec.format = PA_SAMPLE_FLOAT32NE; break; } - mSpec.rate = mDevice->Frequency; + mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); mAttr.maxlength = ~0u; - mAttr.tlength = mDevice->BufferSize * frame_size; + mAttr.tlength = mDevice->mBufferSize * frame_size; mAttr.prebuf = 0u; - mAttr.minreq = mDevice->UpdateSize * frame_size; + mAttr.minreq = mDevice->mUpdateSize * frame_size; mAttr.fragsize = ~0u; mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); + + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); - if(mDevice->Frequency != mSpec.rate) + if(mDevice->mSampleRate != mSpec.rate) { /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ - const auto scale = static_cast(mSpec.rate) / mDevice->Frequency; - const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0); + const auto scale = static_cast(mSpec.rate) / mDevice->mSampleRate; + const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0); const auto bufmax = uint{std::numeric_limits::max()} / mFrameSize; - const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0, + const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0, static_cast(bufmax)); mAttr.maxlength = ~0u; @@ -981,16 +1048,16 @@ bool PulsePlayback::reset() &mMainloop); plock.waitForOperation(op); - mDevice->Frequency = mSpec.rate; + mDevice->mSampleRate = mSpec.rate; } - auto attr_callback = [](pa_stream *stream, void *pdata) noexcept + 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->BufferSize = mAttr.tlength / mFrameSize; - mDevice->UpdateSize = mAttr.minreq / mFrameSize; + mDevice->mBufferSize = mAttr.tlength / mFrameSize; + mDevice->mUpdateSize = mAttr.minreq / mFrameSize; return true; } @@ -1009,8 +1076,9 @@ void PulsePlayback::start() pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } - pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept - { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }, this); + constexpr auto stream_write = [](pa_stream *stream, size_t nbytes, void *pdata) noexcept + { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }; + pa_stream_set_write_callback(mStream, stream_write, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; @@ -1030,7 +1098,7 @@ void PulsePlayback::stop() ClockLatency PulsePlayback::getClockLatency() { - ClockLatency ret; + ClockLatency ret{}; pa_usec_t latency{}; int neg{}, err{}; @@ -1047,8 +1115,8 @@ ClockLatency PulsePlayback::getClockLatency() * server yet. Give a generic value since nothing better is available. */ if(err != -PA_ERR_NODATA) - ERR("Failed to get stream latency: 0x%x\n", err); - latency = mDevice->BufferSize - mDevice->UpdateSize; + ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); + latency = mDevice->mBufferSize - mDevice->mUpdateSize; neg = 0; } else if(neg) UNLIKELY @@ -1060,7 +1128,7 @@ ClockLatency PulsePlayback::getClockLatency() struct PulseCapture final : public BackendBase { - PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PulseCapture() override; void streamStateCallback(pa_stream *stream) noexcept; @@ -1076,7 +1144,7 @@ struct PulseCapture final : public BackendBase { PulseMainloop mMainloop; - std::optional mDeviceName{std::nullopt}; + std::optional mDeviceId{std::nullopt}; al::span mCapBuffer; size_t mHoleLength{0}; @@ -1099,7 +1167,7 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { - ERR("Received stream failure!\n"); + ERR("Received stream failure!"); mDevice->handleDisconnect("Capture stream failure"); } mMainloop.signal(); @@ -1112,13 +1180,13 @@ void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, i mMainloop.signal(); return; } - mDevice->DeviceName = info->description; + mDeviceName = info->description; } void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept { - mDeviceName = pa_stream_get_device_name(stream); - TRACE("Stream moved to %s\n", mDeviceName->c_str()); + mDeviceId = pa_stream_get_device_name(stream); + TRACE("Stream moved to {}", *mDeviceId); } @@ -1132,20 +1200,20 @@ void PulseCapture::open(std::string_view name) "Failed to start device mainloop"}; } - const char *pulse_name{nullptr}; + auto pulse_name = std::string{}; if(!name.empty()) { - if(CaptureDevices.empty()) - mMainloop.probeCaptureDevices(); - auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; + + auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%.*s\" not found", al::sizei(name), name.data()}; - pulse_name = iter->device_name.c_str(); - mDevice->DeviceName = iter->name; + "Device name \"{}\" not found", name}; + + pulse_name = iter->device_name; + mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; @@ -1154,30 +1222,17 @@ void PulseCapture::open(std::string_view name) pa_channel_map chanmap{}; switch(mDevice->FmtChans) { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtX714: - chanmap = X714ChanMap; - break; + case 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, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } setDefaultWFXChannelOrder(); @@ -1201,39 +1256,44 @@ void PulseCapture::open(std::string_view name) case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } - mSpec.rate = mDevice->Frequency; + mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); - const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)}; + const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate*100u/1000u)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0u; - mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size; + mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50u/1000u) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); + TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); - if(pulse_name) mDeviceName.emplace(pulse_name); - else mDeviceName.reset(); - if(mDevice->DeviceName.empty()) + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); + + if(!pulse_name.empty()) + mDeviceId.emplace(std::move(pulse_name)); + + if(mDeviceName.empty()) { - auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, + void *pdata) noexcept { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; @@ -1299,7 +1359,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples) const pa_stream_state_t state{pa_stream_get_state(mStream)}; if(!PA_STREAM_IS_GOOD(state)) UNLIKELY { - mDevice->handleDisconnect("Bad capture state: %u", state); + mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state)); break; } @@ -1307,7 +1367,7 @@ void PulseCapture::captureSamples(std::byte *buffer, uint samples) size_t caplen{}; if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { - mDevice->handleDisconnect("Failed retrieving capture samples: %s", + mDevice->handleDisconnect("Failed retrieving capture samples: {}", pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } @@ -1335,8 +1395,8 @@ uint PulseCapture::availableSamples() if(static_cast(got) < 0) UNLIKELY { const char *err{pa_strerror(static_cast(got))}; - ERR("pa_stream_readable_size() failed: %s\n", err); - mDevice->handleDisconnect("Failed getting readable size: %s", err); + ERR("pa_stream_readable_size() failed: {}", err); + mDevice->handleDisconnect("Failed getting readable size: {}", err); } else { @@ -1359,7 +1419,7 @@ uint PulseCapture::availableSamples() ClockLatency PulseCapture::getClockLatency() { - ClockLatency ret; + ClockLatency ret{}; pa_usec_t latency{}; int neg{}, err{}; @@ -1371,7 +1431,7 @@ ClockLatency PulseCapture::getClockLatency() if(err != 0) UNLIKELY { - ERR("Failed to get stream latency: 0x%x\n", err); + ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); latency = 0; neg = 0; } @@ -1387,7 +1447,7 @@ ClockLatency PulseCapture::getClockLatency() bool PulseBackendFactory::init() { -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD if(!pulse_handle) { #ifdef _WIN32 @@ -1400,7 +1460,7 @@ bool PulseBackendFactory::init() pulse_handle = LoadLib(PALIB); if(!pulse_handle) { - WARN("Failed to load %s\n", PALIB); + WARN("Failed to load {}", PALIB); return false; } @@ -1414,13 +1474,13 @@ bool PulseBackendFactory::init() if(!missing_funcs.empty()) { - WARN("Missing expected functions:%s\n", missing_funcs.c_str()); + WARN("Missing expected functions:{}", missing_funcs); CloseLib(pulse_handle); pulse_handle = nullptr; return false; } } -#endif /* HAVE_DYNLOAD */ +#endif pulse_ctx_flags = PA_CONTEXT_NOFLAGS; if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) @@ -1450,28 +1510,36 @@ bool PulseBackendFactory::init() bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PulseBackendFactory::probe(BackendType type) +auto PulseBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; - auto add_device = [&outnames](const DevMap &entry) -> void + auto add_playback_device = [&outnames](const DevMap &entry) -> void { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); + if(entry.device_name == DefaultPlaybackDevName) + outnames.emplace(outnames.cbegin(), entry.name); + else + outnames.push_back(entry.name); + }; + auto add_capture_device = [&outnames](const DevMap &entry) -> void + { + if(entry.device_name == DefaultCaptureDevName) + outnames.emplace(outnames.cbegin(), entry.name); + else + outnames.push_back(entry.name); }; + auto plock = MainloopUniqueLock{gGlobalMainloop}; switch(type) { case BackendType::Playback: - gGlobalMainloop.probePlaybackDevices(); - std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); + outnames.reserve(PlaybackDevices.size()); + std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device); break; case BackendType::Capture: - gGlobalMainloop.probeCaptureDevices(); - std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); + outnames.reserve(CaptureDevices.size()); + std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device); break; } @@ -1499,9 +1567,9 @@ alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventTyp { case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: + case alc::EventType::DefaultDeviceChanged: return alc::EventSupport::FullSupport; - case alc::EventType::DefaultDeviceChanged: case alc::EventType::Count: break; } diff --git a/libs/openal-soft/alc/backends/pulseaudio.h b/libs/openal-soft/alc/backends/pulseaudio.h index 4752a891..c183d479 100644 --- a/libs/openal-soft/alc/backends/pulseaudio.h +++ b/libs/openal-soft/alc/backends/pulseaudio.h @@ -1,21 +1,27 @@ #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: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PULSEAUDIO_H */ diff --git a/libs/openal-soft/alc/backends/sdl2.cpp b/libs/openal-soft/alc/backends/sdl2.cpp index 17ef502a..a0e41f4a 100644 --- a/libs/openal-soft/alc/backends/sdl2.cpp +++ b/libs/openal-soft/alc/backends/sdl2.cpp @@ -28,10 +28,8 @@ #include #include -#include "almalloc.h" #include "alnumeric.h" #include "core/device.h" -#include "core/logging.h" _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") @@ -41,18 +39,13 @@ _Pragma("GCC diagnostic pop") namespace { -#ifdef _WIN32 -#define DEVNAME_PREFIX "OpenAL Soft on " -#else -#define DEVNAME_PREFIX "" -#endif +using namespace std::string_view_literals; -constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } -constexpr auto getDefaultDeviceName() noexcept -> std::string_view -{ return DEVNAME_PREFIX "Default Device"; } +[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return "Default Device"sv; } struct Sdl2Backend final : public BackendBase { - Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; @@ -62,13 +55,9 @@ struct Sdl2Backend final : public BackendBase { void start() override; void stop() override; + std::string mSDLName; SDL_AudioDeviceID mDeviceID{0u}; uint mFrameSize{0}; - - uint mFrequency{0u}; - DevFmtChannels mFmtChans{}; - DevFmtType mFmtType{}; - uint mUpdateSize{0u}; }; Sdl2Backend::~Sdl2Backend() @@ -89,7 +78,7 @@ void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; - want.freq = static_cast(mDevice->Frequency); + want.freq = static_cast(mDevice->mSampleRate); switch(mDevice->FmtType) { case DevFmtUByte: want.format = AUDIO_U8; break; @@ -100,8 +89,9 @@ void Sdl2Backend::open(std::string_view name) case DevFmtInt: want.format = AUDIO_S32SYS; break; case DevFmtFloat: want.format = AUDIO_F32; break; } - want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(std::min(mDevice->UpdateSize, 8192u)); + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); want.callback = [](void *ptr, Uint8 *stream, int len) noexcept { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; @@ -110,45 +100,21 @@ void Sdl2Backend::open(std::string_view name) * necessarily the first in the list. */ const auto defaultDeviceName = getDefaultDeviceName(); - SDL_AudioDeviceID devid; if(name.empty() || name == defaultDeviceName) { name = defaultDeviceName; - devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + mSDLName.clear(); + mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); } else { - const auto namePrefix = getDevicePrefix(); - if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix) - { - /* Copy the string_view to a string to ensure it's null terminated - * for this call. - */ - const std::string devname{name.substr(namePrefix.size())}; - devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - else - { - const std::string devname{name}; - devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); - } - } - if(!devid) - throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; - - DevFmtChannels devchans{}; - if(have.channels >= 2) - devchans = DevFmtStereo; - else if(have.channels == 1) - devchans = DevFmtMono; - else - { - SDL_CloseAudioDevice(devid); - throw al::backend_exception{al::backend_error::DeviceError, - "Unhandled SDL channel count: %d", int{have.channels}}; + mSDLName = name; + mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); } + if(!mDeviceID) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; DevFmtType devtype{}; switch(have.format) @@ -160,32 +126,100 @@ void Sdl2Backend::open(std::string_view name) case AUDIO_S32SYS: devtype = DevFmtInt; break; case AUDIO_F32SYS: devtype = DevFmtFloat; break; default: - SDL_CloseAudioDevice(devid); - throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", - have.format}; + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", have.format}; } - if(mDeviceID) - SDL_CloseAudioDevice(mDeviceID); - mDeviceID = devid; - mFrameSize = BytesFromDevFmt(devtype) * have.channels; - mFrequency = static_cast(have.freq); - mFmtChans = devchans; - mFmtType = devtype; - mUpdateSize = have.samples; - mDevice->DeviceName = name; + mDeviceName = name; } bool Sdl2Backend::reset() { - mDevice->Frequency = mFrequency; - mDevice->FmtChans = mFmtChans; - mDevice->FmtType = mFmtType; - mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ + if(mDeviceID) + SDL_CloseAudioDevice(mDeviceID); + mDeviceID = 0; + + auto want = SDL_AudioSpec{}; + want.freq = static_cast(mDevice->mSampleRate); + switch(mDevice->FmtType) + { + case DevFmtUByte: want.format = AUDIO_U8; break; + case DevFmtByte: want.format = AUDIO_S8; break; + case DevFmtUShort: want.format = AUDIO_U16SYS; break; + case DevFmtShort: want.format = AUDIO_S16SYS; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: want.format = AUDIO_S32SYS; break; + case DevFmtFloat: want.format = AUDIO_F32; break; + } + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); + want.callback = [](void *ptr, Uint8 *stream, int len) noexcept + { return static_cast(ptr)->audioCallback(stream, len); }; + want.userdata = this; + + auto have = SDL_AudioSpec{}; + if(mSDLName.empty()) + { + mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } + else + { + mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } + if(!mDeviceID) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; + + if(have.channels != mDevice->channelsFromFmt()) + { + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL channel count: {}", int{have.channels}}; + mDevice->mAmbiOrder = 0; + } + + switch(have.format) + { + case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; + case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; + case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; + case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; + case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; + case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; + default: + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", have.format}; + } + + mFrameSize = BytesFromDevFmt(mDevice->FmtType) * have.channels; + + if(have.freq < int{MinOutputRate}) + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL sample rate: {}", have.freq}; + + mDevice->mSampleRate = static_cast(have.freq); + mDevice->mUpdateSize = have.samples; + mDevice->mBufferSize = std::max(have.size/mFrameSize, mDevice->mUpdateSize*2u); + setDefaultWFXChannelOrder(); + return true; } @@ -209,26 +243,25 @@ bool SDL2BackendFactory::init() bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SDL2BackendFactory::probe(BackendType type) +auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; if(type != BackendType::Playback) return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; + if(num_devices <= 0) + return outnames; - /* Includes null char. */ - outnames += getDefaultDeviceName(); - outnames += '\0'; + outnames.reserve(static_cast(num_devices)+1_uz); + outnames.emplace_back(getDefaultDeviceName()); for(int i{0};i < num_devices;++i) { - outnames += getDevicePrefix(); if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) - outnames += name; + outnames.emplace_back(name); else - outnames += "Unknown Device Name #"+std::to_string(i); - outnames += '\0'; + outnames.emplace_back("Unknown Device Name #"+std::to_string(i)); } return outnames; } diff --git a/libs/openal-soft/alc/backends/sdl2.h b/libs/openal-soft/alc/backends/sdl2.h index 3bd8df86..8c582912 100644 --- a/libs/openal-soft/alc/backends/sdl2.h +++ b/libs/openal-soft/alc/backends/sdl2.h @@ -5,15 +5,15 @@ struct SDL2BackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL2_H */ diff --git a/libs/openal-soft/alc/backends/sdl3.cpp b/libs/openal-soft/alc/backends/sdl3.cpp new file mode 100644 index 00000000..b10ad591 --- /dev/null +++ b/libs/openal-soft/alc/backends/sdl3.cpp @@ -0,0 +1,393 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2024 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "sdl3.h" + +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "core/device.h" +#include "core/logging.h" + +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#include "SDL3/SDL_audio.h" +#include "SDL3/SDL_init.h" +#include "SDL3/SDL_stdinc.h" +_Pragma("GCC diagnostic pop") + + +namespace { + +using namespace std::string_view_literals; + +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; +_Pragma("GCC diagnostic pop") + + +template +struct SdlDeleter { + /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */ + void operator()(gsl::owner ptr) const { SDL_free(ptr); } +}; +template +using unique_sdl_ptr = std::unique_ptr>; + + +struct DeviceEntry { + std::string mName; + SDL_AudioDeviceID mPhysDeviceID{}; +}; + +std::vector gPlaybackDevices; + +void EnumeratePlaybackDevices() +{ + auto numdevs = int{}; + auto devicelist = unique_sdl_ptr{SDL_GetAudioPlaybackDevices(&numdevs)}; + if(!devicelist || numdevs < 0) + { + ERR("Failed to get playback devices: {}", SDL_GetError()); + return; + } + + auto devids = al::span{devicelist.get(), static_cast(numdevs)}; + auto newlist = std::vector{}; + + newlist.reserve(devids.size()); + std::transform(devids.begin(), devids.end(), std::back_inserter(newlist), + [](SDL_AudioDeviceID id) + { + auto *name = SDL_GetAudioDeviceName(id); + if(!name) return DeviceEntry{}; + TRACE("Got device \"{}\", ID {}", name, id); + return DeviceEntry{name, id}; + }); + + gPlaybackDevices.swap(newlist); +} + +[[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return "Default Device"sv; } + + +struct Sdl3Backend final : public BackendBase { + explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { } + ~Sdl3Backend() final; + + void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept; + + void open(std::string_view name) final; + auto reset() -> bool final; + void start() final; + void stop() final; + + SDL_AudioDeviceID mDeviceID{0}; + SDL_AudioStream *mStream{nullptr}; + uint mNumChannels{0}; + uint mFrameSize{0}; + std::vector mBuffer; +}; + +Sdl3Backend::~Sdl3Backend() +{ + if(mStream) + SDL_DestroyAudioStream(mStream); + mStream = nullptr; +} + +void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) + noexcept +{ + if(additional_amount < 0) + additional_amount = total_amount; + if(additional_amount <= 0) + return; + + const auto ulen = static_cast(additional_amount); + assert((ulen % mFrameSize) == 0); + + if(ulen > mBuffer.size()) + { + mBuffer.resize(ulen); + std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) + ? std::byte{0x80} : std::byte{}); + } + + mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels); + SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount); +} + +void Sdl3Backend::open(std::string_view name) +{ + const auto defaultDeviceName = getDefaultDeviceName(); + if(name.empty() || name == defaultDeviceName) + { + name = defaultDeviceName; + mDeviceID = DefaultPlaybackDeviceID; + } + else + { + if(gPlaybackDevices.empty()) + EnumeratePlaybackDevices(); + + const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(), + [name](const DeviceEntry &entry) { return name == entry.mName; }); + if(iter == gPlaybackDevices.cend()) + throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name}; + + mDeviceID = iter->mPhysDeviceID; + } + + mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr); + if(!mStream) + throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; + + auto have = SDL_AudioSpec{}; + auto update_size = int{}; + if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size)) + { + auto devtype = mDevice->FmtType; + switch(have.format) + { + case SDL_AUDIO_U8: devtype = DevFmtUByte; break; + case SDL_AUDIO_S8: devtype = DevFmtByte; break; + case SDL_AUDIO_S16: devtype = DevFmtShort; break; + case SDL_AUDIO_S32: devtype = DevFmtInt; break; + case SDL_AUDIO_F32: devtype = DevFmtFloat; break; + default: break; + } + mDevice->FmtType = devtype; + + if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate}) + mDevice->mSampleRate = static_cast(have.freq); + + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + mDevice->mAmbiOrder = 0; + + mNumChannels = static_cast(have.channels); + mFrameSize = mDevice->bytesFromFmt() * mNumChannels; + + if(update_size >= 64) + { + /* We have to assume the total buffer size is just twice the update + * size. SDL doesn't tell us the full end-to-end buffer latency. + */ + mDevice->mUpdateSize = static_cast(update_size); + mDevice->mBufferSize = mDevice->mUpdateSize*2u; + } + else + ERR("Invalid update size from SDL stream: {}", update_size); + } + else + ERR("Failed to get format from SDL stream: {}", SDL_GetError()); + + mDeviceName = name; +} + +auto Sdl3Backend::reset() -> bool +{ + static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount, + int total_amount) noexcept + { + return static_cast(ptr)->audioCallback(stream, additional_amount, + total_amount); + }; + + if(mStream) + SDL_DestroyAudioStream(mStream); + mStream = nullptr; + + mBuffer.clear(); + mBuffer.shrink_to_fit(); + + auto want = SDL_AudioSpec{}; + if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr)) + ERR("Failed to get device format: {}", SDL_GetError()); + + if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate}) + want.freq = static_cast(mDevice->mSampleRate); + if(mDevice->Flags.test(SampleTypeRequest) + || !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8 + || want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32 + || want.format == SDL_AUDIO_F32)) + { + switch(mDevice->FmtType) + { + case DevFmtUByte: want.format = SDL_AUDIO_U8; break; + case DevFmtByte: want.format = SDL_AUDIO_S8; break; + case DevFmtUShort: [[fallthrough]]; + case DevFmtShort: want.format = SDL_AUDIO_S16; break; + case DevFmtUInt: [[fallthrough]]; + case DevFmtInt: want.format = SDL_AUDIO_S32; break; + case DevFmtFloat: want.format = SDL_AUDIO_F32; break; + } + } + if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1) + want.channels = static_cast(std::min(mDevice->channelsFromFmt(), + std::numeric_limits::max())); + + mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this); + if(!mStream) + { + /* If creating the stream failed, try again without a specific format. */ + mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this); + if(!mStream) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to recreate stream: {}", SDL_GetError()}; + } + + auto update_size = int{}; + auto have = SDL_AudioSpec{}; + SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size); + + have = SDL_AudioSpec{}; + if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get stream format: {}", SDL_GetError()}; + + if(!mDevice->Flags.test(ChannelsRequest) + || (static_cast(have.channels) != mDevice->channelsFromFmt() + && !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2))) + { + /* SDL guarantees these layouts for the given channel count. */ + if(have.channels == 8) + mDevice->FmtChans = DevFmtX71; + else if(have.channels == 7) + mDevice->FmtChans = DevFmtX61; + else if(have.channels == 6) + mDevice->FmtChans = DevFmtX51; + else if(have.channels == 4) + mDevice->FmtChans = DevFmtQuad; + else if(have.channels >= 2) + mDevice->FmtChans = DevFmtStereo; + else if(have.channels == 1) + mDevice->FmtChans = DevFmtMono; + else + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL channel count: {}", have.channels}; + mDevice->mAmbiOrder = 0; + } + mNumChannels = static_cast(have.channels); + + switch(have.format) + { + case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; + case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break; + case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break; + case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break; + case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break; + default: + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL format: {:#04x}", al::to_underlying(have.format)}; + } + + mFrameSize = mDevice->bytesFromFmt() * mNumChannels; + + if(have.freq < int{MinOutputRate}) + throw al::backend_exception{al::backend_error::DeviceError, + "Unhandled SDL sample rate: {}", have.freq}; + mDevice->mSampleRate = static_cast(have.freq); + + if(update_size >= 64) + { + mDevice->mUpdateSize = static_cast(update_size); + mDevice->mBufferSize = mDevice->mUpdateSize*2u; + + mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize); + std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) + ? std::byte{0x80} : std::byte{}); + } + else + ERR("Invalid update size from SDL stream: {}", update_size); + + setDefaultWFXChannelOrder(); + + return true; +} + +void Sdl3Backend::start() +{ SDL_ResumeAudioStreamDevice(mStream); } + +void Sdl3Backend::stop() +{ SDL_PauseAudioStreamDevice(mStream); } + +} // namespace + +auto SDL3BackendFactory::getFactory() -> BackendFactory& +{ + static SDL3BackendFactory factory{}; + return factory; +} + +auto SDL3BackendFactory::init() -> bool +{ + if(!SDL_InitSubSystem(SDL_INIT_AUDIO)) + return false; + TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver()); + return true; +} + +auto SDL3BackendFactory::querySupport(BackendType type) -> bool +{ return type == BackendType::Playback; } + +auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector +{ + auto outnames = std::vector{}; + + if(type != BackendType::Playback) + return outnames; + + EnumeratePlaybackDevices(); + outnames.reserve(gPlaybackDevices.size()+1); + outnames.emplace_back(getDefaultDeviceName()); + std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames), + std::mem_fn(&DeviceEntry::mName)); + + return outnames; +} + +auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr +{ + if(type == BackendType::Playback) + return BackendPtr{new Sdl3Backend{device}}; + return nullptr; +} diff --git a/libs/openal-soft/alc/backends/sdl3.h b/libs/openal-soft/alc/backends/sdl3.h new file mode 100644 index 00000000..4d673065 --- /dev/null +++ b/libs/openal-soft/alc/backends/sdl3.h @@ -0,0 +1,19 @@ +#ifndef BACKENDS_SDL3_H +#define BACKENDS_SDL3_H + +#include "base.h" + +struct SDL3BackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_SDL3_H */ diff --git a/libs/openal-soft/alc/backends/sndio.cpp b/libs/openal-soft/alc/backends/sndio.cpp index 2985a2ae..187e1305 100644 --- a/libs/openal-soft/alc/backends/sndio.cpp +++ b/libs/openal-soft/alc/backends/sndio.cpp @@ -20,27 +20,23 @@ #include "config.h" -#include "sndio.h" +#include "sndio.hpp" -#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 "ringbuffer.h" -#include /* NOLINT(*-duplicate-include) Not the same header. */ +#include namespace { @@ -56,7 +52,7 @@ struct SioPar : public sio_par { }; struct SndioPlayback final : public BackendBase { - SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); @@ -102,7 +98,7 @@ int SndioPlayback::mixerProc() size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; if(wrote > buffer.size() || wrote == 0) { - ERR("sio_write failed: 0x%" PRIx64 "\n", wrote); + ERR("sio_write failed: {:#x}", wrote); mDevice->handleDisconnect("Failed to write playback samples"); break; } @@ -119,8 +115,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -130,7 +126,7 @@ void SndioPlayback::open(std::string_view name) sio_close(mSndHandle); mSndHandle = sndHandle; - mDevice->DeviceName = name; + mDeviceName = name; } bool SndioPlayback::reset() @@ -172,12 +168,12 @@ bool SndioPlayback::reset() par.le = SIO_LE_NATIVE; par.msb = 1; - par.rate = mDevice->Frequency; + par.rate = mDevice->mSampleRate; par.pchan = mDevice->channelsFromFmt(); - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; + par.round = mDevice->mUpdateSize; + par.appbufsz = mDevice->mBufferSize - mDevice->mUpdateSize; + if(!par.appbufsz) par.appbufsz = mDevice->mUpdateSize; try { if(!sio_setpar(mSndHandle, &par)) @@ -191,10 +187,10 @@ bool SndioPlayback::reset() if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; + "{}-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 (%u of %u bits)", par.bits, par.bps*8}; + "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"}; @@ -217,24 +213,24 @@ bool SndioPlayback::reset() mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; else throw al::backend_exception{al::backend_error::DeviceError, - "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8}; + "Unhandled sample format: {} {}-bit", (par.sig?"signed":"unsigned"), par.bps*8}; mFrameStep = par.pchan; if(par.pchan != mDevice->channelsFromFmt()) { - WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s", + 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->Frequency = par.rate; + mDevice->mSampleRate = par.rate; setDefaultChannelOrder(); - mDevice->UpdateSize = par.round; - mDevice->BufferSize = par.bufsz + par.round; + mDevice->mUpdateSize = par.round; + mDevice->mBufferSize = par.bufsz + par.round; - mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps); + mBuffer.resize(size_t{mDevice->mUpdateSize} * par.pchan*par.bps); if(par.sig == 1) std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) @@ -254,12 +250,12 @@ void SndioPlayback::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this}; + 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: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -270,7 +266,7 @@ void SndioPlayback::stop() mThread.join(); if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); + ERR("Error stopping device"); } @@ -280,7 +276,7 @@ void SndioPlayback::stop() * capture buffer sizes apps may request. */ struct SndioCapture final : public BackendBase { - SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); @@ -316,7 +312,7 @@ int SndioCapture::recordProc() int nfds_pre{sio_nfds(mSndHandle)}; if(nfds_pre <= 0) { - mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre); + mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre); return 1; } @@ -329,15 +325,14 @@ int SndioCapture::recordProc() const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; if(nfds <= 0) { - mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); + mDevice->handleDisconnect("Failed to get polling fds: {}", nfds); break; } int pollres{::poll(fds.data(), fds.size(), 2000)}; if(pollres < 0) { if(errno == EINTR) continue; - mDevice->handleDisconnect("Poll error: %s", - std::generic_category().message(errno).c_str()); + mDevice->handleDisconnect("Poll error: {}", std::generic_category().message(errno)); break; } if(pollres == 0) @@ -353,7 +348,7 @@ int SndioCapture::recordProc() continue; auto data = mRing->getWriteVector(); - al::span buffer{data.first.buf, data.first.len*frameSize}; + al::span buffer{data[0].buf, data[0].len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; @@ -361,8 +356,8 @@ int SndioCapture::recordProc() break; if(got > buffer.size()) { - ERR("sio_read failed: 0x%" PRIx64 "\n", got); - mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got); + ERR("sio_read failed: {:#x}", got); + mDevice->handleDisconnect("sio_read failed: {:#x}", got); break; } @@ -371,7 +366,7 @@ int SndioCapture::recordProc() if(buffer.empty()) { data = mRing->getWriteVector(); - buffer = {data.first.buf, data.first.len*frameSize}; + buffer = {data[0].buf, data[0].len*frameSize}; } } if(buffer.empty()) @@ -391,8 +386,8 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -427,16 +422,16 @@ void SndioCapture::open(std::string_view name) break; case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, - "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; + "{} 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->Frequency; + par.rate = mDevice->mSampleRate; - par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); - par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); + 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, @@ -444,10 +439,10 @@ void SndioCapture::open(std::string_view name) if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; + "{}-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 %u of %u bits)", par.bits, par.bps*8}; + "Padded samples not supported (got {} of {} bits)", par.bits, par.bps*8}; auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool { @@ -459,19 +454,19 @@ void SndioCapture::open(std::string_view name) || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); }; if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan - || mDevice->Frequency != par.rate) + || mDevice->mSampleRate != par.rate) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead", + "Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead", DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), - mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; + mDevice->mSampleRate, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); - mDevice->BufferSize = static_cast(mRing->writeSpace()); - mDevice->UpdateSize = par.round; + mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false); + mDevice->mBufferSize = static_cast(mRing->writeSpace()); + mDevice->mUpdateSize = par.round; setDefaultChannelOrder(); - mDevice->DeviceName = name; + mDeviceName = name; } void SndioCapture::start() @@ -481,12 +476,12 @@ void SndioCapture::start() try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this}; + 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: %s", e.what()}; + "Failed to start capture thread: {}", e.what()}; } } @@ -497,7 +492,7 @@ void SndioCapture::stop() mThread.join(); if(!sio_stop(mSndHandle)) - ERR("Error stopping device\n"); + ERR("Error stopping device"); } void SndioCapture::captureSamples(std::byte *buffer, uint samples) @@ -520,16 +515,15 @@ bool SndIOBackendFactory::init() bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string SndIOBackendFactory::probe(BackendType type) +auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Include null char. */ - return std::string{GetDefaultName()} + '\0'; + return std::vector{std::string{GetDefaultName()}}; } - return std::string{}; + return {}; } BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/sndio.h b/libs/openal-soft/alc/backends/sndio.h deleted file mode 100644 index d9433191..00000000 --- a/libs/openal-soft/alc/backends/sndio.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BACKENDS_SNDIO_H -#define BACKENDS_SNDIO_H - -#include "base.h" - -struct SndIOBackendFactory final : public BackendFactory { -public: - bool init() override; - - bool querySupport(BackendType type) override; - - std::string probe(BackendType type) override; - - BackendPtr createBackend(DeviceBase *device, BackendType type) override; - - static BackendFactory &getFactory(); -}; - -#endif /* BACKENDS_SNDIO_H */ diff --git a/libs/openal-soft/alc/backends/sndio.hpp b/libs/openal-soft/alc/backends/sndio.hpp new file mode 100644 index 00000000..4327524f --- /dev/null +++ b/libs/openal-soft/alc/backends/sndio.hpp @@ -0,0 +1,19 @@ +#ifndef BACKENDS_SNDIO_HPP +#define BACKENDS_SNDIO_HPP + +#include "base.h" + +struct SndIOBackendFactory final : public BackendFactory { +public: + auto init() -> bool final; + + auto querySupport(BackendType type) -> bool final; + + auto enumerate(BackendType type) -> std::vector final; + + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; +}; + +#endif /* BACKENDS_SNDIO_HPP */ diff --git a/libs/openal-soft/alc/backends/solaris.cpp b/libs/openal-soft/alc/backends/solaris.cpp index da2b4032..c42015e7 100644 --- a/libs/openal-soft/alc/backends/solaris.cpp +++ b/libs/openal-soft/alc/backends/solaris.cpp @@ -60,7 +60,7 @@ std::string solaris_driver{"/dev/audio"}; struct SolarisBackend final : public BackendBase { - SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~SolarisBackend() override; int mixerProc(); @@ -106,13 +106,13 @@ int SolarisBackend::mixerProc() { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno)); + ERR("poll failed: {}", strerror(errno)); + mDevice->handleDisconnect("Failed to wait for playback buffer: {}", strerror(errno)); break; } else if(pret == 0) { - WARN("poll timeout\n"); + WARN("poll timeout"); continue; } @@ -126,8 +126,8 @@ int SolarisBackend::mixerProc() { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; - ERR("write failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno)); + ERR("write failed: {}", strerror(errno)); + mDevice->handleDisconnect("Failed to write playback samples: {}", strerror(errno)); break; } @@ -144,19 +144,19 @@ 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 \"%.*s\" not found", - al::sizei(name), name.data()}; + 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 %s: %s", - solaris_driver.c_str(), strerror(errno)}; + throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", + solaris_driver, strerror(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; - mDevice->DeviceName = name; + mDeviceName = name; } bool SolarisBackend::reset() @@ -164,7 +164,7 @@ bool SolarisBackend::reset() audio_info_t info; AUDIO_INITINFO(&info); - info.play.sample_rate = mDevice->Frequency; + info.play.sample_rate = mDevice->mSampleRate; info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { @@ -187,11 +187,11 @@ bool SolarisBackend::reset() info.play.encoding = AUDIO_ENCODING_LINEAR; break; } - info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt(); + info.play.buffer_size = mDevice->mBufferSize * mDevice->frameSizeFromFmt(); if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) { - ERR("ioctl failed: %s\n", strerror(errno)); + ERR("ioctl failed: {}", strerror(errno)); return false; } @@ -203,7 +203,7 @@ bool SolarisBackend::reset() mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, - "Got %u device channels", info.play.channels}; + "Got {} device channels", info.play.channels}; } if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) @@ -216,20 +216,20 @@ bool SolarisBackend::reset() mDevice->FmtType = DevFmtInt; else { - ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding); + ERR("Got unhandled sample type: {} ({:#x})", info.play.precision, info.play.encoding); return false; } uint frame_size{mDevice->bytesFromFmt() * info.play.channels}; mFrameStep = info.play.channels; - mDevice->Frequency = info.play.sample_rate; - mDevice->BufferSize = info.play.buffer_size / frame_size; + mDevice->mSampleRate = info.play.sample_rate; + mDevice->mBufferSize = info.play.buffer_size / frame_size; /* How to get the actual period size/count? */ - mDevice->UpdateSize = mDevice->BufferSize / 2; + mDevice->mUpdateSize = mDevice->mBufferSize / 2; setDefaultChannelOrder(); - mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); + mBuffer.resize(mDevice->mUpdateSize * size_t{frame_size}); std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; @@ -239,11 +239,11 @@ void SolarisBackend::start() { try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this}; + mThread = std::thread{&SolarisBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -254,7 +254,7 @@ void SolarisBackend::stop() mThread.join(); if(ioctl(mFd, AUDIO_DRAIN) < 0) - ERR("Error draining device: %s\n", strerror(errno)); + ERR("Error draining device: {}", strerror(errno)); } } // namespace @@ -275,19 +275,19 @@ bool SolarisBackendFactory::init() bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SolarisBackendFactory::probe(BackendType type) +auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0) - return std::string{GetDefaultName()} + '\0'; + return std::vector{std::string{GetDefaultName()}}; break; case BackendType::Capture: break; } - return std::string{}; + return {}; } BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/solaris.h b/libs/openal-soft/alc/backends/solaris.h index 5da6ad3a..8415c362 100644 --- a/libs/openal-soft/alc/backends/solaris.h +++ b/libs/openal-soft/alc/backends/solaris.h @@ -5,15 +5,15 @@ struct SolarisBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SOLARIS_H */ diff --git a/libs/openal-soft/alc/backends/wasapi.cpp b/libs/openal-soft/alc/backends/wasapi.cpp index d32fc775..9f944968 100644 --- a/libs/openal-soft/alc/backends/wasapi.cpp +++ b/libs/openal-soft/alc/backends/wasapi.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -68,13 +68,13 @@ #include "comptr.h" #include "core/converter.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" +#include "fmt/chrono.h" #include "ringbuffer.h" #include "strutils.h" -#if defined(ALSOFT_UWP) - +#if ALSOFT_UWP #include // !!This is important!! #include #include @@ -82,11 +82,7 @@ #include #include -using namespace winrt; -using namespace Windows::Foundation; -using namespace Windows::Media::Devices; -using namespace Windows::Devices::Enumeration; -using namespace Windows::Media::Devices; +#include "alstring.h" #endif /* Some headers seem to define these as macros for __uuidof, which is annoying @@ -99,7 +95,7 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif -#if !defined(ALSOFT_UWP) +#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 ); @@ -107,18 +103,24 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x 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; -[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } - using ReferenceTime = std::chrono::duration>; -constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept -{ return ReferenceTime{static_cast(n)}; } - #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) @@ -129,7 +131,7 @@ constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept #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 inline DWORD MaskFromTopBits(DWORD b) noexcept +constexpr auto MaskFromTopBits(DWORD b) noexcept -> DWORD { b |= b>>1; b |= b>>2; @@ -161,9 +163,6 @@ constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObje constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight}; -constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft - | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency - | AudioObjectType_BackLeft | AudioObjectType_BackRight}; constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackCenter}; @@ -175,6 +174,13 @@ constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObje | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft | AudioObjectType_TopBackRight}; +constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft + | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft + | AudioObjectType_BottomBackRight}; template @@ -185,16 +191,15 @@ overloaded(Ts...) -> overloaded; template -constexpr auto as_unsigned(T value) noexcept -{ - using UT = std::make_unsigned_t; - return static_cast(value); -} +struct CoTaskMemDeleter { + void operator()(T *ptr) const { CoTaskMemFree(ptr); } +}; +template +using unique_coptr = std::unique_ptr>; /* Scales the given reftime value, rounding the result. */ -template -constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept +constexpr auto RefTime2Samples(const ReferenceTime &val, DWORD srate) noexcept -> uint { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; return static_cast(std::min(retval, std::numeric_limits::max())); @@ -202,34 +207,66 @@ constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept class GuidPrinter { - std::array mMsg; + std::string mMsg; public: - GuidPrinter(const GUID &guid) - { - std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", - DWORD{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 c_str() const -> const char* { return mMsg.data(); } + explicit GuidPrinter(const GUID &guid) + : mMsg{fmt::format( + "{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}", + guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7])} + { } + [[nodiscard]] auto str() const noexcept -> const std::string& { return mMsg; } }; struct PropVariant { - PROPVARIANT mProp; + PROPVARIANT mProp{}; public: PropVariant() { PropVariantInit(&mProp); } + PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); } ~PropVariant() { clear(); } + auto operator=(const PropVariant &rhs) -> PropVariant& + { + if(this != &rhs) + PropVariantCopy(&mProp, &rhs.mProp); + return *this; + } + void clear() { PropVariantClear(&mProp); } PROPVARIANT* get() noexcept { return &mProp; } - PROPVARIANT& operator*() noexcept { return mProp; } - const PROPVARIANT& operator*() const noexcept { return mProp; } + /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ + [[nodiscard]] + auto type() const noexcept -> VARTYPE { return mProp.vt; } - PROPVARIANT* operator->() noexcept { return &mProp; } - const PROPVARIANT* operator->() const noexcept { return &mProp; } + template [[nodiscard]] + auto value() const -> T + { + if constexpr(std::is_same_v) + { + alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT); + return mProp.uintVal; + } + else if constexpr(std::is_same_v || std::is_same_v + || std::is_same_v || std::is_same_v) + { + alassert(mProp.vt == VT_LPWSTR); + return mProp.pwszVal; + } + } + + void setBlob(const al::span data) + { + if constexpr(sizeof(size_t) > sizeof(ULONG)) + alassert(data.size() <= std::numeric_limits::max()); + mProp.vt = VT_BLOB; + mProp.blob.cbSize = static_cast(data.size()); + mProp.blob.pBlobData = data.data(); + } + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ }; struct DevMap { @@ -281,9 +318,18 @@ struct DeviceListLock : public std::unique_lock { }; DeviceList gDeviceList; +std::condition_variable_any gInitCV; +std::atomic gInitDone{false}; -#if defined(ALSOFT_UWP) +#ifdef AVRTAPI +struct AvrtHandleCloser { + void operator()(HANDLE handle) { AvRevertMmThreadCharacteristics(handle); } +}; +using AvrtHandlePtr = std::unique_ptr,AvrtHandleCloser>; +#endif + +#if ALSOFT_UWP enum EDataFlow { eRender = 0, eCapture = (eRender + 1), @@ -292,7 +338,7 @@ enum EDataFlow { }; #endif -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; using EventRegistrationToken = winrt::event_token; #else @@ -300,43 +346,42 @@ using DeviceHandle = ComPtr; #endif -using NameGUIDPair = std::pair; -static NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) +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 !defined(ALSOFT_UWP) - std::string name, guid; - - ComPtr ps; - HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; +#if !ALSOFT_UWP + auto ps = ComPtr{}; + auto hr = device->OpenPropertyStore(STGM_READ, al::out_ptr(ps)); if(FAILED(hr)) { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return {std::string{UnknownName}, std::string{UnknownGuid}}; + WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); + return NameGUIDPair{std::string{UnknownName}, std::string{UnknownGuid}}; } - PropVariant pvprop; + auto ret = NameGUIDPair{}; + auto pvprop = PropVariant{}; hr = ps->GetValue(al::bit_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) - WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - else if(pvprop->vt == VT_LPWSTR) - name = wstr_to_utf8(pvprop->pwszVal); + 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: 0x%04x\n", pvprop->vt); + WARN("Unexpected Device_FriendlyName PROPVARIANT type: {:#04x}", pvprop.type()); pvprop.clear(); hr = ps->GetValue(al::bit_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) - WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - else if(pvprop->vt == VT_LPWSTR) - guid = wstr_to_utf8(pvprop->pwszVal); + 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: 0x%04x\n", pvprop->vt); + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: {:#04x}", pvprop.type()); #else - std::string name{wstr_to_utf8(device.Name())}; - std::string guid; + 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"}.")) @@ -344,25 +389,25 @@ static NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) devIdStart += 2; // L"}." if(auto devIdStartEnd = wcschr(devIdStart, L'#')) { - std::wstring wDevId{devIdStart, static_cast(devIdStartEnd - devIdStart)}; - guid = wstr_to_utf8(wDevId.c_str()); - std::transform(guid.begin(), guid.end(), guid.begin(), + ret.mGuid = wstr_to_utf8(std::wstring_view{devIdStart, + static_cast(devIdStartEnd - devIdStart)}); + std::transform(ret.mGuid.begin(), ret.mGuid.end(), ret.mGuid.begin(), [](char ch) { return static_cast(std::toupper(ch)); }); } } #endif - if(name.empty()) name = UnknownName; - if(guid.empty()) guid = UnknownGuid; - return {std::move(name), std::move(guid)}; + if(ret.mName.empty()) ret.mName = UnknownName; + if(ret.mGuid.empty()) ret.mGuid = UnknownGuid; + return ret; } -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr ps; HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); + WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); return UnknownFormFactor; } @@ -370,68 +415,73 @@ EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) PropVariant pvform; hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) - WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); - else if(pvform->vt == VT_UI4) - formfactor = static_cast(pvform->ulVal); - else if(pvform->vt != VT_EMPTY) - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + 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 defined(ALSOFT_UWP) -struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler -#else -struct DeviceHelper final : private IMMNotificationClient -#endif -{ -#if defined(ALSOFT_UWP) - DeviceHelper() +#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); - mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) { - if (args.Role() == AudioDeviceRole::Default) + static constexpr auto playback_cb = [](const IInspectable &sender [[maybe_unused]], + const DefaultAudioRenderDeviceChangedEventArgs &args) + { + if(args.Role() == AudioDeviceRole::Default) { - const std::string msg{ "Default playback device changed: " + + const auto msg = std::string{"Default playback device changed: " + wstr_to_utf8(args.Id())}; - alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, - msg); + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); } - }); + }; + mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged(playback_cb); - mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) { - if (args.Role() == AudioDeviceRole::Default) + static constexpr auto capture_cb = [](const IInspectable &sender [[maybe_unused]], + const DefaultAudioCaptureDeviceChangedEventArgs &args) + { + if(args.Role() == AudioDeviceRole::Default) { - const std::string msg{ "Default capture device changed: " + - wstr_to_utf8(args.Id()) }; - alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, - msg); + 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); } -#else - DeviceHelper() = default; -#endif - ~DeviceHelper() + + ~DeviceEnumHelper() { -#if defined(ALSOFT_UWP) MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); if(mActiveClientEvent != nullptr) CloseHandle(mActiveClientEvent); mActiveClientEvent = nullptr; + } #else + +struct DeviceEnumHelper final : private IMMNotificationClient { + DeviceEnumHelper() = default; + ~DeviceEnumHelper() + { if(mEnumerator) mEnumerator->UnregisterEndpointNotificationCallback(this); mEnumerator = nullptr; -#endif } +#endif + + template + auto as() noexcept -> T { return T{this}; } /** -------------------------- IUnknown ----------------------------- */ std::atomic mRefCount{1}; @@ -457,24 +507,24 @@ struct DeviceHelper final : private IMMNotificationClient // 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 defined(ALSOFT_UWP) +#if ALSOFT_UWP if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } #else if(IId == __uuidof(IMMNotificationClient)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } #endif else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) { - *UnknownPtrPtr = static_cast(this); + *UnknownPtrPtr = as(); AddRef(); return S_OK; } @@ -484,7 +534,7 @@ struct DeviceHelper final : private IMMNotificationClient return E_NOINTERFACE; } -#if defined(ALSOFT_UWP) +#if ALSOFT_UWP /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override { @@ -495,82 +545,42 @@ struct DeviceHelper final : private IMMNotificationClient } #else /** ----------------------- IMMNotificationClient ------------ */ - STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } - - STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override + STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) noexcept override { - ComPtr device; - HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; - if(FAILED(hr)) - { - ERR("Failed to get device: 0x%08lx\n", hr); - return S_OK; - } + TRACE("OnDeviceStateChanged({}, {:#x})", deviceId ? wstr_to_utf8(deviceId) + : std::string{""}, newState); - ComPtr endpoint; - hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); - if(FAILED(hr)) - { - ERR("Failed to get device endpoint: 0x%08lx\n", hr); - return S_OK; - } - - EDataFlow flowdir{}; - hr = endpoint->GetDataFlow(&flowdir); - if(FAILED(hr)) - { - ERR("Failed to get endpoint data flow: 0x%08lx\n", hr); - return S_OK; - } - - auto devlock = DeviceListLock{gDeviceList}; - auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); - - if(AddDevice(device, pwstrDeviceId, list)) - { - const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback - : alc::DeviceType::Capture; - const std::string msg{"Device added: "+list.back().name}; - alc::Event(alc::EventType::DeviceAdded, devtype, msg); - } - - return S_OK; + if(!(newState&DEVICE_STATE_ACTIVE)) + return DeviceRemoved(deviceId); + return DeviceAdded(deviceId); } - STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override + STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) noexcept override { - auto devlock = DeviceListLock{gDeviceList}; - for(auto flowdir : std::array{eRender, eCapture}) - { - auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); - auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; - - /* Find the ID in the list to remove. */ - auto iter = std::find_if(list.begin(), list.end(), - [pwstrDeviceId](const DevMap &entry) noexcept - { return pwstrDeviceId == entry.devid; }); - if(iter == list.end()) continue; - - TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(), - iter->endpoint_guid.c_str(), iter->devid.c_str()); - - std::string msg{"Device removed: "+std::move(iter->name)}; - list.erase(iter); - - alc::Event(alc::EventType::DeviceRemoved, devtype, msg); - } - return S_OK; + 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 pwstrDefaultDeviceId) noexcept override + 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 std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId - : std::wstring_view{}}; + const auto devid = defaultDeviceId ? std::wstring_view{defaultDeviceId} + : std::wstring_view{}; if(flow == eRender) { DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); @@ -587,95 +597,34 @@ struct DeviceHelper final : private IMMNotificationClient } #endif - /** -------------------------- DeviceHelper ----------------------------- */ + /** ------------------------ DeviceEnumHelper -------------------------- */ HRESULT init() { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; if(SUCCEEDED(hr)) mEnumerator->RegisterEndpointNotificationCallback(this); else - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); return hr; #else return S_OK; #endif } - HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device) - { -#if !defined(ALSOFT_UWP) - HRESULT hr{E_FAIL}; - if(mEnumerator) - { - if(devid.empty()) - hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); - else - hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device)); - } - return hr; -#else - const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; - auto devIfPath = - devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) - : winrt::hstring(devid.data()); - 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; -#endif - } - -#if !defined(ALSOFT_UWP) - static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) - { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } -#else - HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) - { - 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: 0x%lx\n", 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); - } -#endif - std::wstring probeDevices(EDataFlow flowdir, std::vector &list) { std::wstring defaultId; std::vector{}.swap(list); -#if !defined(ALSOFT_UWP) +#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: 0x%08lx\n", hr); + ERR("Failed to enumerate audio endpoints: {:#x}", as_unsigned(hr)); return defaultId; } @@ -688,11 +637,11 @@ struct DeviceHelper final : private IMMNotificationClient hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); if(SUCCEEDED(hr)) { - if(WCHAR *devid{GetDeviceId(device.get())}) - { - defaultId = devid; - CoTaskMemFree(devid); - } + auto devid = unique_coptr{}; + if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) + defaultId = devid.get(); + else + ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } @@ -702,11 +651,11 @@ struct DeviceHelper final : private IMMNotificationClient if(FAILED(hr)) continue; - if(WCHAR *devid{GetDeviceId(device.get())}) - { - std::ignore = AddDevice(device, devid, list); - CoTaskMemFree(devid); - } + auto devid = unique_coptr{}; + if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) + std::ignore = AddDevice(device, devid.get(), list); + else + ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } #else @@ -754,37 +703,93 @@ private: return false; } - auto name_guid = GetDeviceNameAndGuid(device); - int count{1}; - std::string newname{name_guid.first}; + auto [name, guid] = GetDeviceNameAndGuid(device); + auto count = 1; + auto newname = name; while(checkName(list, newname)) - { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(name_guid.second), devid); - const DevMap &newentry = list.back(); + newname = fmt::format("{} #{}", name, ++count); + const auto &newentry = list.emplace_back(std::move(newname), std::move(guid), devid); - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); + TRACE("Got device \"{}\", \"{}\", \"{}\"", newentry.name, newentry.endpoint_guid, + wstr_to_utf8(newentry.devid)); return true; } -#if !defined(ALSOFT_UWP) - static WCHAR *GetDeviceId(IMMDevice *device) +#if !ALSOFT_UWP + STDMETHODIMP DeviceAdded(LPCWSTR deviceId) noexcept { - WCHAR *devid; - - const HRESULT hr{device->GetId(&devid)}; + auto device = ComPtr{}; + auto hr = mEnumerator->GetDevice(deviceId, al::out_ptr(device)); if(FAILED(hr)) { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; + ERR("Failed to get device: {:#x}", as_unsigned(hr)); + return S_OK; } - return devid; + 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 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; } + + STDMETHODIMP DeviceRemoved(LPCWSTR deviceId) noexcept + { + auto devlock = DeviceListLock{gDeviceList}; + for(auto flowdir : std::array{eRender, eCapture}) + { + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; + + /* Find the ID in the list to remove. */ + auto iter = std::find_if(list.begin(), list.end(), + [deviceId](const DevMap &entry) noexcept { return deviceId == entry.devid; }); + if(iter == list.end()) continue; + + TRACE("Removing device \"{}\", \"{}\", \"{}\"", iter->name, iter->endpoint_guid, + wstr_to_utf8(iter->devid)); + + std::string msg{"Device removed: "+std::move(iter->name)}; + list.erase(iter); + + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + return S_OK; + } + ComPtr mEnumerator{nullptr}; #else @@ -794,8 +799,239 @@ private: EventRegistrationToken mRenderDeviceChangedToken; EventRegistrationToken mCaptureDeviceChangedToken; #endif + + static inline std::mutex mMsgLock; + static inline std::condition_variable mMsgCond; + static inline bool mQuit{false}; + + [[nodiscard]] + static bool quit() + { + auto lock = std::unique_lock{mMsgLock}; + mMsgCond.wait(lock, []{return mQuit;}); + return mQuit; + } + +public: + static void messageHandler(std::promise *promise); }; +/* Manages a DeviceEnumHelper on its own thread, to track available devices. */ +void DeviceEnumHelper::messageHandler(std::promise *promise) +{ + TRACE("Starting watcher thread"); + + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) + { + WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); + promise->set_value(com.status()); + return; + } + + 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); + + gInitDone.store(true, std::memory_order_relaxed); + } + catch(std::exception &e) { + ERR("Exception probing devices: {}", e.what()); + if(promise) + promise->set_value(E_FAIL); + return; + } + + TRACE("Watcher thread started"); + gInitCV.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(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) -> HRESULT + { + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto devIfPath = + devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : winrt::hstring(devid.c_str()); + if (devIfPath.empty()) + return E_POINTER; + + auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, + DeviceInformationKind::DeviceInterface).get(); + if(!deviceInfo) + return E_NOINTERFACE; + device = deviceInfo; + return S_OK; + } + + [[nodiscard]] + auto activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) -> HRESULT + { + ComPtr asyncOp; + HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, + al::out_ptr(asyncOp))}; + if(FAILED(hr)) + return hr; + + /* I don't like waiting for INFINITE time, but the activate operation + * can take an indefinite amount of time since it can require user + * input. + */ + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; + if(res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: {:#x}", res); + return E_FAIL; + } + + HRESULT hrActivateRes{E_FAIL}; + ComPtr punkAudioIface; + hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); + if(SUCCEEDED(hr)) hr = hrActivateRes; + if(FAILED(hr)) return hr; + + return punkAudioIface->QueryInterface(iid, ppv); + } + + HANDLE mActiveClientEvent{nullptr}; +}; + +#else + +struct DeviceHelper { + DeviceHelper() = default; + ~DeviceHelper() = default; + + [[nodiscard]] + auto init() -> HRESULT + { + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; + if(FAILED(hr)) + WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); + return hr; + } + + [[nodiscard]] + auto openDevice(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) const + -> HRESULT + { + HRESULT hr{E_FAIL}; + if(mEnumerator) + { + if(devid.empty()) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid.c_str(), al::out_ptr(device)); + } + return hr; + } + + [[nodiscard]] + static auto activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) -> HRESULT + { + 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{}; @@ -808,259 +1044,130 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { 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: %d\n", out->Format.nChannels); + 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: %d\n", out->Format.nChannels); + ERR("Unhandled IEEE float channel count: {}", out->Format.nChannels); out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; } else { - ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); + ERR("Unhandled format tag: {:#06x}", in->wFormatTag); return false; } return true; } -void TraceFormat(const char *msg, const WAVEFORMATEX *format) +void TraceFormat(const std::string_view msg, const WAVEFORMATEX *format) { constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) { - const WAVEFORMATEXTENSIBLE *fmtex{ - CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n" - " Samples = %d\n" - " ChannelMask = 0x%lx\n" - " SubFormat = %s\n", + 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}.c_str()); + GuidPrinter{fmtex->SubFormat}.str()); + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ } else - TRACE("%s:\n" - " FormatTag = 0x%04x\n" - " Channels = %d\n" - " SamplesPerSec = %lu\n" - " AvgBytesPerSec = %lu\n" - " BlockAlign = %d\n" - " BitsPerSample = %d\n" - " Size = %d\n", + 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); } -enum class MsgType { - OpenDevice, - ResetDevice, - StartDevice, - StopDevice, - CloseDevice, - - QuitThread -}; - -constexpr const char *GetMessageTypeName(MsgType type) noexcept +/* Duplicates the first sample of each sample frame to the second sample, at + * half volume. Essentially converting mono to stereo. + */ +template +void DuplicateSamples(al::span insamples, size_t step) { - switch(type) + auto samples = al::span{reinterpret_cast(insamples.data()), insamples.size()/sizeof(T)}; + if constexpr(std::is_floating_point_v) { - case MsgType::OpenDevice: return "Open Device"; - case MsgType::ResetDevice: return "Reset Device"; - case MsgType::StartDevice: return "Start Device"; - case MsgType::StopDevice: return "Stop Device"; - case MsgType::CloseDevice: return "Close Device"; - case MsgType::QuitThread: break; + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = samples[i] * T{0.5}; + samples[i+1] = samples[i] = s; + } + } + else if constexpr(std::is_signed_v) + { + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = samples[i] / 2; + samples[i+1] = samples[i] = T(s); + } + } + else + { + using ST = std::make_signed_t; + static constexpr auto SignBit = T{1u << (sizeof(T)*8 - 1)}; + for(size_t i{0};i < samples.size();i+=step) + { + const auto s = static_cast(samples[i]^SignBit) / 2; + samples[i+1] = samples[i] = T(s)^SignBit; + } + } +} + +void DuplicateSamples(al::span insamples, DevFmtType sampletype, size_t step) +{ + switch(sampletype) + { + case DevFmtByte: return DuplicateSamples(insamples, step); + case DevFmtUByte: return DuplicateSamples(insamples, step); + case DevFmtShort: return DuplicateSamples(insamples, step); + case DevFmtUShort: return DuplicateSamples(insamples, step); + case DevFmtInt: return DuplicateSamples(insamples, step); + case DevFmtUInt: return DuplicateSamples(insamples, step); + case DevFmtFloat: return DuplicateSamples(insamples, step); } - return ""; } -/* Proxy interface used by the message handler. */ -struct WasapiProxy { - virtual ~WasapiProxy() = default; - - virtual HRESULT openProxy(std::string_view name) = 0; - virtual void closeProxy() = 0; - - virtual HRESULT resetProxy() = 0; - virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; - - struct Msg { - MsgType mType; - WasapiProxy *mProxy; - std::string_view mParam; - std::promise mPromise; - - explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } - }; - static inline std::deque mMsgQueue; - static inline std::mutex mMsgQueueLock; - static inline std::condition_variable mMsgQueueCond; - - static inline std::optional sDeviceHelper; - - std::future pushMessage(MsgType type, std::string_view param={}) - { - std::promise promise; - std::future future{promise.get_future()}; - { - std::lock_guard msglock{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static std::future pushMessageStatic(MsgType type) - { - std::promise promise; - std::future future{promise.get_future()}; - { - std::lock_guard msglock{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)}); - } - mMsgQueueCond.notify_one(); - return future; - } - - static Msg popMessage() - { - std::unique_lock lock{mMsgQueueLock}; - mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); - Msg msg{std::move(mMsgQueue.front())}; - mMsgQueue.pop_front(); - return msg; - } - - static int messageHandler(std::promise *promise); -}; - -int WasapiProxy::messageHandler(std::promise *promise) -{ - TRACE("Starting message thread\n"); - - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - WARN("Failed to initialize COM: 0x%08lx\n", com.status()); - promise->set_value(com.status()); - return 0; - } - - HRESULT hr{sDeviceHelper.emplace().init()}; - promise->set_value(hr); - promise = nullptr; - if(FAILED(hr)) - goto skip_loop; - - { - auto devlock = DeviceListLock{gDeviceList}; - auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList()); - if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); - defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList()); - if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); - } - - TRACE("Starting message loop\n"); - while(Msg msg{popMessage()}) - { - TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n", - GetMessageTypeName(msg.mType), static_cast(msg.mType), - static_cast(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data()); - - switch(msg.mType) - { - case MsgType::OpenDevice: - hr = msg.mProxy->openProxy(msg.mParam); - msg.mPromise.set_value(hr); - continue; - - case MsgType::ResetDevice: - hr = msg.mProxy->resetProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StartDevice: - hr = msg.mProxy->startProxy(); - msg.mPromise.set_value(hr); - continue; - - case MsgType::StopDevice: - msg.mProxy->stopProxy(); - msg.mPromise.set_value(S_OK); - continue; - - case MsgType::CloseDevice: - msg.mProxy->closeProxy(); - msg.mPromise.set_value(S_OK); - continue; - - case MsgType::QuitThread: - break; - } - ERR("Unexpected message: %u\n", static_cast(msg.mType)); - msg.mPromise.set_value(E_FAIL); - } - TRACE("Message loop finished\n"); - -skip_loop: - sDeviceHelper.reset(); - - return 0; -} - -struct WasapiPlayback final : public BackendBase, WasapiProxy { - WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } +struct WasapiPlayback final : public BackendBase { + explicit WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; - int mixerProc(); - int mixerSpatialProc(); - - void open(std::string_view name) override; - HRESULT openProxy(std::string_view name) override; - void closeProxy() override; - - bool reset() override; - HRESULT resetProxy() override; - void start() override; - HRESULT startProxy() override; - void stop() override; - void stopProxy() override; - - ClockLatency getClockLatency() override; - - void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); - void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); - - HRESULT mOpenStatus{E_FAIL}; - DeviceHandle mMMDev{nullptr}; - struct PlainDevice { ComPtr mClient{nullptr}; ComPtr mRender{nullptr}; @@ -1070,28 +1177,80 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ComPtr mRender{nullptr}; AudioObjectType mStaticMask{}; }; - std::variant mAudio; + + void mixerProc(PlainDevice &audio); + void mixerProc(SpatialDevice &audio); + + auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) + -> HRESULT; + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + auto initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) -> bool; + auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + std::variant &audiodev) -> HRESULT; + + void proc_thread(std::string&& name); + + + void open(std::string_view name) override; + bool reset() override; + void start() override; + void stop() override; + + ClockLatency getClockLatency() override; + + + std::thread mProcThread; + std::mutex mProcMutex; + std::condition_variable mProcCond; + HRESULT mProcResult{E_FAIL}; + + enum class ThreadState : uint8_t { + Initializing, + Waiting, + Playing, + Done + }; + ThreadState mState{ThreadState::Initializing}; + + enum class ThreadAction : uint8_t { + Nothing, + Configure, + Play, + Quit + }; + ThreadAction mAction{ThreadAction::Nothing}; + + + static inline DWORD sAvIndex{}; + HANDLE mNotifyEvent{nullptr}; - UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; - std::vector mResampleBuffer{}; + UINT32 mOutBufferSize{}, mOutUpdateSize{}; + std::vector mResampleBuffer; uint mBufferFilled{0}; SampleConverterPtr mResampler; + bool mMonoUpsample{false}; + bool mExclusiveMode{false}; WAVEFORMATEXTENSIBLE mFormat{}; std::atomic mPadding{0u}; std::mutex mMutex; - std::atomic mKillNow{true}; - std::thread mThread; }; WasapiPlayback::~WasapiPlayback() { - if(SUCCEEDED(mOpenStatus)) - pushMessage(MsgType::CloseDevice).wait(); - mOpenStatus = E_FAIL; + if(mProcThread.joinable()) + { + { + auto plock = std::lock_guard{mProcMutex}; + mKillNow = true; + mAction = ThreadAction::Quit; + } + mProcCond.notify_all(); + mProcThread.join(); + } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -1099,159 +1258,194 @@ WasapiPlayback::~WasapiPlayback() } -FORCE_ALIGN int WasapiPlayback::mixerProc() +FORCE_ALIGN void WasapiPlayback::mixerProc(PlainDevice &audio) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; - } + class PriorityControl { + const int mOldPriority; - auto &audio = std::get(mAudio); - - SetRTPriority(); - althrd_setname(GetMixerThreadName()); + public: + PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} + { + if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + ERR("Failed to set priority level for thread"); + } + ~PriorityControl() + { SetThreadPriority(GetCurrentThread(), mOldPriority); } + }; + auto prioctrl = PriorityControl{}; const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; - const uint update_size{mOrigUpdateSize}; - const UINT32 buffer_len{mOrigBufferSize}; + const UINT32 buffer_len{mOutBufferSize}; const void *resbufferptr{}; + assert(buffer_len > 0); + +#ifdef AVRTAPI + /* TODO: "Audio" or "Pro Audio"? The suggestion is to use "Pro Audio" for + * device periods less than 10ms, and "Audio" for greater than or equal to + * 10ms. + */ + auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; + auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; +#endif + + auto prefilling = true; mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { - UINT32 written; - HRESULT hr{audio.mClient->GetCurrentPadding(&written)}; - if(FAILED(hr)) + /* 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) { - ERR("Failed to get padding: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr); - break; - } - mPadding.store(written, std::memory_order_relaxed); - - uint len{buffer_len - written}; - if(len < update_size) - { - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - continue; - } - - BYTE *buffer; - hr = audio.mRender->GetBuffer(len, &buffer); - if(SUCCEEDED(hr)) - { - if(mResampler) + if(auto hr = audio.mClient->GetCurrentPadding(&written); FAILED(hr)) { - std::lock_guard dlock{mMutex}; - for(UINT32 done{0};done < len;) + ERR("Failed to get padding: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to retrieve buffer padding: {:#x}", + as_unsigned(hr)); + break; + } + mPadding.store(written, std::memory_order_relaxed); + } + + if(const auto len = uint{buffer_len - written}) + { + auto buffer = LPBYTE{}; + auto hr = audio.mRender->GetBuffer(len, &buffer); + if(SUCCEEDED(hr)) + { + if(mResampler) { - if(mBufferFilled == 0) + auto dlock = std::lock_guard{mMutex}; + auto dst = al::span{buffer, size_t{len}*frame_size}; + for(UINT32 done{0};done < len;) { - mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize, - mFormat.Format.nChannels); - resbufferptr = mResampleBuffer.data(); - mBufferFilled = mDevice->UpdateSize; + if(mBufferFilled == 0) + { + mDevice->renderSamples(mResampleBuffer.data(), mDevice->mUpdateSize, + mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.data(); + mBufferFilled = mDevice->mUpdateSize; + } + + const auto got = mResampler->convert(&resbufferptr, &mBufferFilled, + dst.data(), len-done); + dst = dst.subspan(size_t{got}*frame_size); + done += got; } - - uint got{mResampler->convert(&resbufferptr, &mBufferFilled, buffer, len-done)}; - buffer += got*frame_size; - done += got; - - mPadding.store(written + done, std::memory_order_relaxed); + mPadding.store(written + len, std::memory_order_relaxed); } + else + { + auto dlock = std::lock_guard{mMutex}; + mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); + mPadding.store(written + len, std::memory_order_relaxed); + } + + if(mMonoUpsample) + { + DuplicateSamples(al::span{buffer, size_t{len}*frame_size}, mDevice->FmtType, + mFormat.Format.nChannels); + } + + hr = audio.mRender->ReleaseBuffer(len, 0); } - else + if(FAILED(hr)) { - std::lock_guard dlock{mMutex}; - mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); - mPadding.store(written + len, std::memory_order_relaxed); + ERR("Failed to buffer data: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to send playback samples: {:#x}", + as_unsigned(hr)); + break; } - hr = audio.mRender->ReleaseBuffer(len, 0); } - if(FAILED(hr)) + + if(prefilling) { - ERR("Failed to buffer data: 0x%08lx\n", hr); - mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr); - break; + prefilling = false; + ResetEvent(mNotifyEvent); + if(auto hr = audio.mClient->Start(); FAILED(hr)) + { + ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to start audio client: {:#x}", + as_unsigned(hr)); + break; + } } + + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: {:#x}", res); } mPadding.store(0u, std::memory_order_release); - - return 0; + audio.mClient->Stop(); } -FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() +FORCE_ALIGN void WasapiPlayback::mixerProc(SpatialDevice &audio) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) - { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; - } + class PriorityControl { + int mOldPriority; + public: + PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} + { + if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) + ERR("Failed to set priority level for thread"); + } + ~PriorityControl() + { SetThreadPriority(GetCurrentThread(), mOldPriority); } + }; + auto prioctrl = PriorityControl{}; - auto &audio = std::get(mAudio); - - SetRTPriority(); - althrd_setname(GetMixerThreadName()); +#ifdef AVRTAPI + auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; + auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; +#endif std::vector> channels; - std::vector buffers; - std::vector resbuffers; + std::vector buffers; + std::vector resbuffers; std::vector tmpbuffers; /* TODO: Set mPadding appropriately. There doesn't seem to be a way to * update it dynamically based on the stream, so a fixed size may be the * best we can do. */ - mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release); + mPadding.store(mOutBufferSize-mOutUpdateSize, std::memory_order_release); mBufferFilled = 0; + auto firstupdate = true; while(!mKillNow.load(std::memory_order_relaxed)) { - if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) - { - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); - - HRESULT hr{audio.mRender->Reset()}; - if(FAILED(hr)) - { - ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("Device lost: 0x%08lx", hr); - break; - } - } - UINT32 dynamicCount{}, framesToDo{}; HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; if(SUCCEEDED(hr)) { if(channels.empty()) UNLIKELY { - auto flags = as_unsigned(audio.mStaticMask); + auto flags = as_unsigned(al::to_underlying(audio.mStaticMask)); channels.reserve(as_unsigned(al::popcount(flags))); while(flags) { auto id = decltype(flags){1} << al::countr_zero(flags); flags &= ~id; - channels.emplace_back(); audio.mRender->ActivateSpatialAudioObject(static_cast(id), - al::out_ptr(channels.back())); + 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(size_t i{0};i < tmpbuffers.size();++i) - resbuffers[i] = reinterpret_cast(mResampleBuffer.data()) + - mDevice->UpdateSize*i; + { + resbuffers[i] = al::to_address(bufptr); + bufptr += ptrdiff_t(mDevice->mUpdateSize*sizeof(float)); + } } } @@ -1259,12 +1453,12 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() * update, unfortunately. */ std::transform(channels.cbegin(), channels.cend(), buffers.begin(), - [](const ComPtr &obj) -> float* + [](const ComPtr &obj) -> void* { - BYTE *buffer{}; - UINT32 size{}; + auto buffer = LPBYTE{}; + auto size = UINT32{}; obj->GetBuffer(&buffer, &size); - return reinterpret_cast(buffer); + return buffer; }); if(!mResampler) @@ -1276,15 +1470,15 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() { if(mBufferFilled == 0) { - mDevice->renderSamples(resbuffers, mDevice->UpdateSize); + mDevice->renderSamples(resbuffers, mDevice->mUpdateSize); std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); - mBufferFilled = mDevice->UpdateSize; + mBufferFilled = mDevice->mUpdateSize; } const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, - reinterpret_cast(buffers.data()), framesToDo-pos)}; + buffers.data(), framesToDo-pos)}; for(auto &buf : buffers) - buf += got; + buf = static_cast(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ pos += got; } } @@ -1292,211 +1486,217 @@ FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() 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(DWORD 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: 0x%08lx\n", hr); + ERR("Failed to update playback objects: {:#x}", as_unsigned(hr)); } mPadding.store(0u, std::memory_order_release); + audio.mRender->Stop(); + audio.mRender->Reset(); +} - return 0; + +void WasapiPlayback::proc_thread(std::string&& name) +try { + auto com = ComWrapper{COINIT_MULTITHREADED}; + if(!com) + { + const auto hr = as_unsigned(com.status()); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); + mDevice->handleDisconnect("COM init failed: {:#x}", hr); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = com.status(); + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + if(!gInitDone.load(std::memory_order_relaxed)) + { + auto devlock = DeviceListLock{gDeviceList}; + gInitCV.wait(devlock, []() -> bool { return gInitDone; }); + } + + auto helper = DeviceHelper{}; + if(HRESULT hr{helper.init()}; FAILED(hr)) + { + mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + althrd_setname(GetMixerThreadName()); + + auto mmdev = DeviceHandle{nullptr}; + if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto audiodev = std::variant{}; + + auto plock = std::unique_lock{mProcMutex}; + mProcResult = S_OK; + while(mState != ThreadState::Done) + { + mAction = ThreadAction::Nothing; + mState = ThreadState::Waiting; + mProcCond.notify_all(); + + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); + switch(mAction) + { + case ThreadAction::Nothing: + break; + + case ThreadAction::Configure: + { + plock.unlock(); + const auto hr = resetProxy(helper, mmdev, audiodev); + plock.lock(); + mProcResult = hr; + } + break; + + case ThreadAction::Play: + mKillNow.store(false, std::memory_order_release); + + mAction = ThreadAction::Nothing; + mState = ThreadState::Playing; + mProcResult = S_OK; + plock.unlock(); + mProcCond.notify_all(); + + std::visit([this](auto &audio) -> void { mixerProc(audio); }, audiodev); + + plock.lock(); + break; + + case ThreadAction::Quit: + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); + break; + } + } +} +catch(...) { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = E_FAIL; + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); } void WasapiPlayback::open(std::string_view name) { - if(SUCCEEDED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, - "Unexpected duplicate open call"}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { - ERR("Failed to create notify events: %lu\n", GetLastError()); + ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } - if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) - name = name.substr(prefix.size()); + mProcThread = std::thread{&WasapiPlayback::proc_thread, this, std::string{name}}; - mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); - if(FAILED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - mOpenStatus}; + 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)}; } -HRESULT WasapiPlayback::openProxy(std::string_view name) +auto WasapiPlayback::openProxy(const std::string_view name, DeviceHelper &helper, + DeviceHandle &mmdev) -> HRESULT { - std::string devname; - std::wstring devid; + auto devname = std::string{}; + auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto list = al::span{devlock.getPlaybackList()}; auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); + { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); + { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == list.cend()) { - WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); - return E_FAIL; + WARN("Failed to find device name matching \"{}\"", name); + return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } - HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)}; - if(FAILED(hr)) + if(HRESULT hr{helper.openDevice(devid, eRender, mmdev)}; FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); + WARN("Failed to open device \"{}\": {:#x}", devname.empty() + ? "(default)"sv : std::string_view{devname}, as_unsigned(hr)); return hr; } if(!devname.empty()) - mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; + mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } -void WasapiPlayback::closeProxy() -{ - mAudio.emplace(); - mMMDev = nullptr; -} - - -void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType) -{ - bool isRear51{false}; - - if(!mDevice->Flags.test(FrequencyRequest)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - if(!mDevice->Flags.test(ChannelsRequest)) - { - /* If not requesting a channel configuration, auto-select given what - * fits the mask's lsb (to ensure no gaps in the output channels). If - * there's no mask, we can only assume mono or stereo. - */ - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - { - mDevice->FmtChans = DevFmtX51; - isRear51 = true; - } - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) - mDevice->FmtChans = DevFmtMono; - else - ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); - } - else - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); - } - - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - switch(mDevice->FmtChans) - { - case DevFmtMono: - OutputType.Format.nChannels = 1; - OutputType.dwChannelMask = MONO; - break; - case DevFmtAmbi3D: - mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - 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 DevFmtX714: - OutputType.Format.nChannels = 12; - OutputType.dwChannelMask = X7DOT1DOT4; - break; - } - switch(mDevice->FmtType) - { - case DevFmtByte: - mDevice->FmtType = DevFmtUByte; - /* fall-through */ - case DevFmtUByte: - OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUShort: - mDevice->FmtType = DevFmtShort; - /* fall-through */ - case DevFmtShort: - OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtUInt: - mDevice->FmtType = DevFmtInt; - /* fall-through */ - case DevFmtInt: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - case DevFmtFloat: - OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - break; - } - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - - OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; -} void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) { - if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) - mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) + mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); else - mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec)); + mDevice->mSampleRate = std::min(mDevice->mSampleRate, + uint(OutputType.Format.nSamplesPerSec)); const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; @@ -1514,6 +1714,12 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) { 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)); @@ -1534,6 +1740,7 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) break; case DevFmtX714: chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); + case DevFmtX7144: case DevFmtAmbi3D: break; } @@ -1557,7 +1764,7 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) mDevice->FmtChans = DevFmtMono; else { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + ERR("Unhandled extensible channels: {} -- {:#08x}", OutputType.Format.nChannels, OutputType.dwChannelMask); mDevice->FmtChans = DevFmtStereo; OutputType.Format.nChannels = 2; @@ -1586,385 +1793,547 @@ void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) } else { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); + 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; } -bool WasapiPlayback::reset() +auto WasapiPlayback::initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) + -> bool { - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; - return true; -} - -HRESULT WasapiPlayback::resetProxy() -{ - if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false)) - { - auto &audio = mAudio.emplace(); - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient), - al::out_ptr(audio.mClient))}; - if(FAILED(hr)) - { - ERR("Failed to activate spatial audio client: 0x%08lx\n", hr); - goto no_spatial; - } - - ComPtr fmtenum; - hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); - if(FAILED(hr)) - { - ERR("Failed to get format enumerator: 0x%08lx\n", hr); - goto no_spatial; - } - - UINT32 fmtcount{}; - hr = fmtenum->GetCount(&fmtcount); - if(FAILED(hr) || fmtcount == 0) - { - ERR("Failed to get format count: 0x%08lx\n", hr); - goto no_spatial; - } - - WAVEFORMATEX *preferredFormat{}; - hr = fmtenum->GetFormat(0, &preferredFormat); - if(FAILED(hr)) - { - ERR("Failed to get preferred format: 0x%08lx\n", hr); - goto no_spatial; - } - TraceFormat("Preferred mix format", preferredFormat); - - UINT32 maxFrames{}; - hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); - if(FAILED(hr)) - ERR("Failed to get max frames: 0x%08lx\n", hr); - else - TRACE("Max sample frames: %u\n", maxFrames); - for(UINT32 i{1};i < fmtcount;++i) - { - WAVEFORMATEX *otherFormat{}; - hr = fmtenum->GetFormat(i, &otherFormat); - if(FAILED(hr)) - ERR("Failed to format %u: 0x%08lx\n", i+1, hr); - else - { - TraceFormat("Other mix format", otherFormat); - UINT32 otherMaxFrames{}; - hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); - if(FAILED(hr)) - ERR("Failed to get max frames: 0x%08lx\n", hr); - else - TRACE("Max sample frames: %u\n", otherMaxFrames); - } - } - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, preferredFormat)) - goto no_spatial; - - /* Force 32-bit float. This is currently required for planar output. */ - if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE - && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) - { - OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - OutputType.Format.cbSize = 0; - } - if(OutputType.Format.wBitsPerSample != 32) - { - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u - / OutputType.Format.wBitsPerSample; - OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nBlockAlign * 32 - / OutputType.Format.wBitsPerSample); - OutputType.Format.wBitsPerSample = 32; - } - 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->Frequency = OutputType.Format.nSamplesPerSec; - - bool isRear51{false}; - if(!mDevice->Flags.test(ChannelsRequest)) - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) - { - mDevice->FmtChans = DevFmtX51; - isRear51 = true; - } - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - /* HACK: Don't autoselect mono. Wine returns this and makes the - * audio terrible. - */ - else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))) - ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); - } - else - { - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); - } - - auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept - { - switch(chans) - { - case DevFmtMono: return ChannelMask_Mono; - case DevFmtStereo: return ChannelMask_Stereo; - case DevFmtQuad: return ChannelMask_Quad; - case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51; - case DevFmtX61: return ChannelMask_X61; - case DevFmtX3D71: - case DevFmtX71: return ChannelMask_X71; - case DevFmtX714: return ChannelMask_X714; - case DevFmtAmbi3D: - break; - } - return ChannelMask_Stereo; - }; - - SpatialAudioObjectRenderStreamActivationParams streamParams{}; - streamParams.ObjectFormat = &OutputType.Format; - streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); - streamParams.Category = AudioCategory_Media; - streamParams.EventHandle = mNotifyEvent; - - PropVariant paramProp{}; - paramProp->vt = VT_BLOB; - paramProp->blob.cbSize = sizeof(streamParams); - paramProp->blob.pBlobData = reinterpret_cast(&streamParams); - - hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), - __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); - if(FAILED(hr)) - { - ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr); - goto no_spatial; - } - - 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->DeviceName, "wasapi", "allow-resampler", true)) - mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); - else - mDevice->Frequency = std::min(mDevice->Frequency, - uint(OutputType.Format.nSamplesPerSec)); - - setDefaultWFXChannelOrder(); - - /* FIXME: Get the real update and buffer size. Presumably the actual - * device is configured once ActivateSpatialAudioStream succeeds, and - * an IAudioClient from the same IMMDevice accesses the same device - * configuration. This isn't obviously correct, but for now assume - * IAudioClient::GetDevicePeriod returns the current device period time - * that ISpatialAudioObjectRenderStream will try to wake up at. - * - * Unfortunately this won't get the buffer size of the - * ISpatialAudioObjectRenderStream, so we only assume there's two - * periods. - */ - mOrigUpdateSize = mDevice->UpdateSize; - mOrigBufferSize = mOrigUpdateSize*2; - ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; - - ComPtr tmpClient; - hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), - al::out_ptr(tmpClient)); - if(FAILED(hr)) - ERR("Failed to activate audio client: 0x%08lx\n", hr); - else - { - hr = tmpClient->GetDevicePeriod(&reinterpret_cast(per_time), nullptr); - if(FAILED(hr)) - ERR("Failed to get device period: 0x%08lx\n", hr); - else - { - mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); - mOrigBufferSize = mOrigUpdateSize*2; - } - } - tmpClient = nullptr; - - mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency); - mDevice->BufferSize = mDevice->UpdateSize*2; - - mResampler = nullptr; - mResampleBuffer.clear(); - mResampleBuffer.shrink_to_fit(); - mBufferFilled = 0; - if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) - { - const auto flags = as_unsigned(streamParams.StaticObjectTypeMask); - const auto channelCount = as_unsigned(al::popcount(flags)); - mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, - channelCount, mDevice->Frequency, mFormat.Format.nSamplesPerSec, - Resampler::FastBSinc24); - mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount * - mFormat.Format.wBitsPerSample / 8); - - TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", - DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, - mDevice->UpdateSize); - } - - return S_OK; - } - -no_spatial: - mDevice->Flags.reset(Virtualization); - - auto &audio = mAudio.emplace(); - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + HRESULT hr{helper.activateAudioClient(mmdev, __uuidof(ISpatialAudioClient), al::out_ptr(audio.mClient))}; if(FAILED(hr)) { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); - return hr; + ERR("Failed to activate spatial audio client: {:#x}", as_unsigned(hr)); + return false; } - WAVEFORMATEX *wfx; - hr = audio.mClient->GetMixFormat(&wfx); + ComPtr fmtenum; + hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); if(FAILED(hr)) { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; + ERR("Failed to get format enumerator: {:#x}", as_unsigned(hr)); + return false; + } + + UINT32 fmtcount{}; + hr = fmtenum->GetCount(&fmtcount); + if(FAILED(hr) || fmtcount == 0) + { + ERR("Failed to get format count: {:#08x}", as_unsigned(hr)); + return false; + } + + WAVEFORMATEX *preferredFormat{}; + hr = fmtenum->GetFormat(0, &preferredFormat); + if(FAILED(hr)) + { + ERR("Failed to get preferred format: {:#x}", as_unsigned(hr)); + return false; + } + TraceFormat("Preferred mix format", preferredFormat); + + UINT32 maxFrames{}; + hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); + else + TRACE("Max sample frames: {}", maxFrames); + for(UINT32 i{1};i < fmtcount;++i) + { + WAVEFORMATEX *otherFormat{}; + hr = fmtenum->GetFormat(i, &otherFormat); + if(FAILED(hr)) + ERR("Failed to get format {}: {:#x}", i+1, as_unsigned(hr)); + else + { + TraceFormat("Other mix format", otherFormat); + UINT32 otherMaxFrames{}; + hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); + else + TRACE("Max sample frames: {}", otherMaxFrames); + } } - TraceFormat("Device mix format", wfx); WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) + 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) { - CoTaskMemFree(wfx); - return E_FAIL; + OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + OutputType.Format.cbSize = 0; } - CoTaskMemFree(wfx); - wfx = nullptr; + if(OutputType.Format.wBitsPerSample != 32) + { + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u + / OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nBlockAlign * 32 + / OutputType.Format.wBitsPerSample); + OutputType.Format.wBitsPerSample = 32; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; - const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + /* Match the output rate if not requesting anything specific. */ + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; - prepareFormat(OutputType); + 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; + }; - TraceFormat("Requesting playback format", &OutputType.Format); - hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + SpatialAudioObjectRenderStreamActivationParams streamParams{}; + streamParams.ObjectFormat = &OutputType.Format; + streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); + streamParams.Category = AudioCategory_Media; + streamParams.EventHandle = mNotifyEvent; + + PropVariant paramProp{}; + paramProp.setBlob({reinterpret_cast(&streamParams), sizeof(streamParams)}); + + hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), + __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); if(FAILED(hr)) { - WARN("Failed to check format support: 0x%08lx\n", hr); - hr = audio.mClient->GetMixFormat(&wfx); + ERR("Failed to activate spatial audio stream: {:#x}", as_unsigned(hr)); + return false; } + + audio.mStaticMask = streamParams.StaticObjectTypeMask; + mFormat = OutputType; + + mDevice->FmtType = DevFmtFloat; + mDevice->Flags.reset(DirectEar).set(Virtualization); + if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) + mDevice->FmtChans = DevFmtStereo; + if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) + mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); + else + mDevice->mSampleRate = std::min(mDevice->mSampleRate, + uint(OutputType.Format.nSamplesPerSec)); + + setDefaultWFXChannelOrder(); + + /* 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 = static_cast((uint64_t{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(al::popcount(flags)); + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, + mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); + mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * channelCount * + mFormat.Format.wBitsPerSample / 8); + + TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, + mDevice->mUpdateSize); + } + + return true; +} + +bool WasapiPlayback::reset() +{ + auto plock = std::unique_lock{mProcMutex}; + if(mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", + unsigned{al::to_underlying(mState)}}; + + mAction = ThreadAction::Configure; + mProcCond.notify_all(); + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Configure; }); + + if(FAILED(mProcResult) || mState != ThreadState::Waiting) + throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", + as_unsigned(mProcResult)}; + return true; +} + +auto WasapiPlayback::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + std::variant &audiodev) -> HRESULT +{ + if(GetConfigValueBool(mDevice->mDeviceName, "wasapi", "spatial-api", false)) + { + if(initSpatial(helper, mmdev, audiodev.emplace())) + return S_OK; + } + + mDevice->Flags.reset(Virtualization); + mMonoUpsample = false; + mExclusiveMode = false; + + auto &audio = audiodev.emplace(); + auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient)); if(FAILED(hr)) { - ERR("Failed to find a supported format: 0x%08lx\n", hr); + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } - if(wfx != nullptr) + auto wfx = unique_coptr{}; + hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); + if(FAILED(hr)) { - TraceFormat("Got playback format", wfx); - if(!MakeExtensible(&OutputType, wfx)) + ERR("Failed to get mix format: {:#x}", as_unsigned(hr)); + return hr; + } + TraceFormat("Device mix format", wfx.get()); + + auto OutputType = WAVEFORMATEXTENSIBLE{}; + if(!MakeExtensible(&OutputType, wfx.get())) + return E_FAIL; + wfx = nullptr; + + /* Get the buffer and update sizes as a ReferenceTime before potentially + * altering the sample rate. + */ + const auto buf_time = ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; + const auto per_time = ReferenceTime{seconds{mDevice->mUpdateSize}} / mDevice->mSampleRate; + + /* Update the mDevice format for non-requested properties. */ + bool isRear51{false}; + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; + if(!mDevice->Flags.test(ChannelsRequest)) + { + /* If not requesting a channel configuration, auto-select given what + * fits the mask's lsb (to ensure no gaps in the output channels). If + * there's no mask, we can only assume mono or stereo. + */ + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) { - CoTaskMemFree(wfx); - return E_FAIL; + mDevice->FmtChans = DevFmtX51; + isRear51 = true; } - CoTaskMemFree(wfx); + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; + else + ERR("Unhandled channel config: {} -- {:#08x}", chancount, chanmask); + } + else + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); + } + + /* Request a format matching the mDevice. */ + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + switch(mDevice->FmtChans) + { + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtAmbi3D: + mDevice->FmtChans = DevFmtStereo; + [[fallthrough]]; + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + case DevFmtX3D71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + [[fallthrough]]; + case DevFmtX714: + OutputType.Format.nChannels = 12; + OutputType.dwChannelMask = X7DOT1DOT4; + break; + } + switch(mDevice->FmtType) + { + case DevFmtByte: + mDevice->FmtType = DevFmtUByte; + [[fallthrough]]; + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + mDevice->FmtType = DevFmtShort; + [[fallthrough]]; + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUInt: + mDevice->FmtType = DevFmtInt; + [[fallthrough]]; + case DevFmtInt: + OutputType.Format.wBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels + * OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec + * OutputType.Format.nBlockAlign; + + const auto sharemode = + GetConfigValueBool(mDevice->mDeviceName, "wasapi", "exclusive-mode", false) + ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED; + mExclusiveMode = (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE); + + TraceFormat("Requesting playback format", &OutputType.Format); + hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, al::out_ptr(wfx)); + if(FAILED(hr)) + { + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + /* For exclusive mode, IAudioClient::IsFormatSupported won't give + * back a supported format. However, a common failure is an + * unsupported sample type, so try a fallback to 16-bit int. + */ + if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT && mDevice->FmtType != DevFmtShort) + { + mDevice->FmtType = DevFmtShort; + + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels + * OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec + * OutputType.Format.nBlockAlign; + + hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, + al::out_ptr(wfx)); + } + } + else + { + WARN("Failed to check format support: {:#x}", as_unsigned(hr)); + hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); + } + } + if(FAILED(hr)) + { + ERR("Failed to find a supported format: {:#x}", as_unsigned(hr)); + return hr; + } + + if(wfx) + { + TraceFormat("Got playback format", wfx.get()); + if(!MakeExtensible(&OutputType, wfx.get())) + return E_FAIL; wfx = nullptr; finalizeFormat(OutputType); } mFormat = OutputType; -#if !defined(ALSOFT_UWP) - const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())}; +#if !ALSOFT_UWP + const EndpointFormFactor formfactor{GetDeviceFormfactor(mmdev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); #else mDevice->Flags.set(DirectEar, false); #endif setDefaultWFXChannelOrder(); - hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - buf_time.count(), 0, &OutputType.Format, nullptr); + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + auto period_time = per_time; + auto min_period = ReferenceTime{}; + hr = audio.mClient->GetDevicePeriod(nullptr, + &reinterpret_cast(min_period)); + if(FAILED(hr)) + ERR("Failed to get minimum period time: {:#x}", as_unsigned(hr)); + else if(min_period > period_time) + { + period_time = min_period; + WARN("Clamping to minimum period time, {}", nanoseconds{min_period}); + } + + hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + period_time.count(), period_time.count(), &OutputType.Format, nullptr); + if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) + { + auto newsize = UINT32{}; + hr = audio.mClient->GetBufferSize(&newsize); + if(SUCCEEDED(hr)) + { + period_time = ReferenceTime{seconds{newsize}} + / OutputType.Format.nSamplesPerSec; + WARN("Adjusting to supported period time, {}", nanoseconds{period_time}); + + audio.mClient = nullptr; + hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient)); + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); + return hr; + } + hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + period_time.count(), period_time.count(), &OutputType.Format, nullptr); + } + } + } + else + hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { - ERR("Failed to initialize audio client: 0x%08lx\n", hr); + ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); return hr; } - UINT32 buffer_len{}; - ReferenceTime min_per{}; - hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + auto buffer_len = UINT32{}; + auto period_time = ReferenceTime{}; + hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(period_time), nullptr); if(SUCCEEDED(hr)) hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { - ERR("Failed to get audio buffer info: 0x%08lx\n", 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: 0x%08lx\n", hr); + ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } - /* Find the nearest multiple of the period size to the update size */ - if(min_per < per_time) - min_per *= std::max((per_time + min_per/2) / min_per, 1_i64); + 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; + } - mOrigBufferSize = buffer_len; - mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), - buffer_len/2u); + mOutBufferSize = buffer_len; + if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) + { + /* For exclusive mode, the buffer size is the update size, and there's + * implicitly two update periods on the device. + */ + mOutUpdateSize = buffer_len; + mDevice->mUpdateSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / + mFormat.Format.nSamplesPerSec); + mDevice->mBufferSize = mDevice->mUpdateSize * 2; + } + else + { + mOutUpdateSize = RefTime2Samples(period_time, mFormat.Format.nSamplesPerSec); - mDevice->BufferSize = static_cast(uint64_t{buffer_len} * mDevice->Frequency / - mFormat.Format.nSamplesPerSec); - mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency), - mDevice->BufferSize/2u); + mDevice->mBufferSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / + mFormat.Format.nSamplesPerSec); + mDevice->mUpdateSize = std::min(RefTime2Samples(period_time, mDevice->mSampleRate), + mDevice->mBufferSize/2u); + } mResampler = nullptr; mResampleBuffer.clear(); mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; - if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + if(mDevice->mSampleRate != mFormat.Format.nSamplesPerSec) { mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, - mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, + mFormat.Format.nChannels, mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); - mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels * + mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); - TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, - mDevice->UpdateSize); + mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, + mDevice->mUpdateSize); } return hr; @@ -1973,143 +2342,90 @@ no_spatial: void WasapiPlayback::start() { - const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start playback: 0x%lx", hr}; + 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)}; } -HRESULT WasapiPlayback::startProxy() -{ - ResetEvent(mNotifyEvent); - - auto mstate_fallback = [](std::monostate) -> HRESULT - { return E_FAIL; }; - auto start_plain = [&](PlainDevice &audio) -> HRESULT - { - HRESULT hr{audio.mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); - if(SUCCEEDED(hr)) - { - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; - } - catch(...) { - audio.mRender = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - if(FAILED(hr)) - audio.mClient->Stop(); - return hr; - }; - auto start_spatial = [&](SpatialDevice &audio) -> HRESULT - { - HRESULT hr{audio.mRender->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start spatial audio stream: 0x%08lx\n", hr); - return hr; - } - - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this}; - } - catch(...) { - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - - if(FAILED(hr)) - { - audio.mRender->Stop(); - audio.mRender->Reset(); - } - return hr; - }; - - return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio); -} - - void WasapiPlayback::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiPlayback::stopProxy() { - if(!mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - auto mstate_fallback = [](std::monostate) -> void - { }; - auto stop_plain = [](PlainDevice &audio) -> void + auto plock = std::unique_lock{mProcMutex}; + if(mState == ThreadState::Playing) { - audio.mRender = nullptr; - audio.mClient->Stop(); - }; - auto stop_spatial = [](SpatialDevice &audio) -> void - { - audio.mRender->Stop(); - audio.mRender->Reset(); - }; - std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio); + mKillNow = true; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Playing; }); + } } ClockLatency WasapiPlayback::getClockLatency() { - ClockLatency ret; - std::lock_guard dlock{mMutex}; + ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; ret.Latency /= mFormat.Format.nSamplesPerSec; if(mResampler) { auto extra = mResampler->currentInputDelay(); - ret.Latency += std::chrono::duration_cast(extra) / mDevice->Frequency; - ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency; + ret.Latency += std::chrono::duration_cast(extra) / mDevice->mSampleRate; + ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->mSampleRate; } return ret; } -struct WasapiCapture final : public BackendBase, WasapiProxy { - WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } +struct WasapiCapture final : public BackendBase { + explicit WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiCapture() override; - int recordProc(); + void recordProc(IAudioClient *client, IAudioCaptureClient *capture); + + void proc_thread(std::string&& name); + + auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) + -> HRESULT; + auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, ComPtr &client, + ComPtr &capture) -> HRESULT; void open(std::string_view name) override; - HRESULT openProxy(std::string_view name) override; - void closeProxy() override; - - HRESULT resetProxy() override; void start() override; - HRESULT startProxy() override; void stop() override; - void stopProxy() override; - void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; - HRESULT mOpenStatus{E_FAIL}; - DeviceHandle mMMDev{nullptr}; - ComPtr mClient{nullptr}; - ComPtr mCapture{nullptr}; + + std::thread mProcThread; + std::mutex mProcMutex; + std::condition_variable mProcCond; + HRESULT mProcResult{E_FAIL}; + + enum class ThreadState : uint8_t { + Initializing, + Waiting, + Recording, + Done + }; + ThreadState mState{ThreadState::Initializing}; + + enum class ThreadAction : uint8_t { + Nothing, + Record, + Quit + }; + ThreadAction mAction{ThreadAction::Nothing}; + + HANDLE mNotifyEvent{nullptr}; ChannelConverter mChannelConv{}; @@ -2122,9 +2438,16 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { - if(SUCCEEDED(mOpenStatus)) - pushMessage(MsgType::CloseDevice).wait(); - mOpenStatus = E_FAIL; + if(mProcThread.joinable()) + { + { + auto plock = std::lock_guard{mProcMutex}; + mKillNow = true; + mAction = ThreadAction::Quit; + } + mProcCond.notify_all(); + mProcThread.join(); + } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -2132,39 +2455,37 @@ WasapiCapture::~WasapiCapture() } -FORCE_ALIGN int WasapiCapture::recordProc() +FORCE_ALIGN void WasapiCapture::recordProc(IAudioClient *client, IAudioCaptureClient *capture) { - ComWrapper com{COINIT_MULTITHREADED}; - if(!com) + ResetEvent(mNotifyEvent); + if(HRESULT hr{client->Start()}; FAILED(hr)) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); - mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); - return 1; + ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); + mDevice->handleDisconnect("Failed to start audio client: {:#x}", as_unsigned(hr)); + return; } - althrd_setname(GetRecordThreadName()); - std::vector samples; while(!mKillNow.load(std::memory_order_relaxed)) { - UINT32 avail; - HRESULT hr{mCapture->GetNextPacketSize(&avail)}; + auto avail = UINT32{}; + auto hr = capture->GetNextPacketSize(&avail); if(FAILED(hr)) - ERR("Failed to get next packet size: 0x%08lx\n", hr); + ERR("Failed to get next packet size: {:#x}", as_unsigned(hr)); else if(avail > 0) { - UINT32 numsamples; - DWORD flags; - BYTE *rdata; + auto numsamples = UINT32{}; + auto flags = DWORD{}; + BYTE *rdata{}; - hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); + hr = capture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); if(FAILED(hr)) - ERR("Failed to get capture buffer: 0x%08lx\n", hr); + ERR("Failed to get capture buffer: {:#x}", as_unsigned(hr)); else { if(mChannelConv.is_active()) { - samples.resize(numsamples*2); + samples.resize(numsamples*2_uz); mChannelConv.convert(rdata, samples.data(), numsamples); rdata = reinterpret_cast(samples.data()); } @@ -2178,166 +2499,257 @@ FORCE_ALIGN int WasapiCapture::recordProc() const void *srcdata{rdata}; uint srcframes{numsamples}; - dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast(std::min(data.first.len, lenlimit))); - if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) + dstframes = mSampleConv->convert(&srcdata, &srcframes, data[0].buf, + static_cast(std::min(data[0].len, lenlimit))); + if(srcframes > 0 && dstframes == data[0].len && data[1].len > 0) { /* If some source samples remain, all of the first dest * block was filled, and there's space in the second * dest block, do another run for the second block. */ - dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast(std::min(data.second.len, lenlimit))); + dstframes += mSampleConv->convert(&srcdata, &srcframes, data[1].buf, + static_cast(std::min(data[1].len, lenlimit))); } } else { const uint framesize{mDevice->frameSizeFromFmt()}; - size_t len1{std::min(data.first.len, size_t{numsamples})}; - size_t len2{std::min(data.second.len, numsamples-len1)}; + auto dst = al::span{rdata, size_t{numsamples}*framesize}; + size_t len1{std::min(data[0].len, size_t{numsamples})}; + size_t len2{std::min(data[1].len, numsamples-len1)}; - memcpy(data.first.buf, rdata, len1*framesize); + memcpy(data[0].buf, dst.data(), len1*framesize); if(len2 > 0) - memcpy(data.second.buf, rdata+len1*framesize, len2*framesize); + { + dst = dst.subspan(len1*framesize); + memcpy(data[1].buf, dst.data(), len2*framesize); + } dstframes = len1 + len2; } mRing->writeAdvance(dstframes); - hr = mCapture->ReleaseBuffer(numsamples); - if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr); + 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: 0x%08lx", hr); + mDevice->handleDisconnect("Failed to capture samples: {:#x}", as_unsigned(hr)); break; } - DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; - if(res != WAIT_OBJECT_0) - ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: {:#x}", res); } - return 0; + client->Stop(); + client->Reset(); +} + + +void WasapiCapture::proc_thread(std::string&& name) +try { + auto com = ComWrapper{COINIT_MULTITHREADED}; + if(!com) + { + const auto hr = as_unsigned(com.status()); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); + mDevice->handleDisconnect("COM init failed: {:#x}", hr); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = com.status(); + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + if(!gInitDone.load(std::memory_order_relaxed)) + { + auto devlock = DeviceListLock{gDeviceList}; + gInitCV.wait(devlock, []() -> bool { return gInitDone; }); + } + + auto helper = DeviceHelper{}; + if(HRESULT hr{helper.init()}; FAILED(hr)) + { + mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); + + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + althrd_setname(GetRecordThreadName()); + + auto mmdev = DeviceHandle{nullptr}; + if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto client = ComPtr{}; + auto capture = ComPtr{}; + if(auto hr = resetProxy(helper, mmdev, client, capture); FAILED(hr)) + { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = hr; + mState = ThreadState::Done; + mProcCond.notify_all(); + return; + } + + auto plock = std::unique_lock{mProcMutex}; + mProcResult = S_OK; + while(mState != ThreadState::Done) + { + mAction = ThreadAction::Nothing; + mState = ThreadState::Waiting; + mProcCond.notify_all(); + + mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); + switch(mAction) + { + case ThreadAction::Nothing: + break; + + case ThreadAction::Record: + mKillNow.store(false, std::memory_order_release); + + mAction = ThreadAction::Nothing; + mState = ThreadState::Recording; + mProcResult = S_OK; + plock.unlock(); + mProcCond.notify_all(); + + recordProc(client.get(), capture.get()); + + plock.lock(); + break; + + case ThreadAction::Quit: + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); + break; + } + } +} +catch(...) { + auto plock = std::lock_guard{mProcMutex}; + mProcResult = E_FAIL; + mAction = ThreadAction::Nothing; + mState = ThreadState::Done; + mProcCond.notify_all(); } void WasapiCapture::open(std::string_view name) { - if(SUCCEEDED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, - "Unexpected duplicate open call"}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { - ERR("Failed to create notify events: %lu\n", GetLastError()); + ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } - if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) - name = name.substr(prefix.size()); + mProcThread = std::thread{&WasapiCapture::proc_thread, this, std::string{name}}; - mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); - if(FAILED(mOpenStatus)) - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - mOpenStatus}; + auto plock = std::unique_lock{mProcMutex}; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Initializing; }); - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - { - if(hr == E_OUTOFMEMORY) - throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; - throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"}; - } + 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)}; } -HRESULT WasapiCapture::openProxy(std::string_view name) +auto WasapiCapture::openProxy(const std::string_view name, DeviceHelper &helper, + DeviceHandle &mmdev) -> HRESULT { - std::string devname; - std::wstring devid; + auto devname = std::string{}; + auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto devlist = al::span{devlock.getCaptureList()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); + { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); + { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == devlist.cend()) { - WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); - return E_FAIL; + WARN("Failed to find device name matching \"{}\"", name); + return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } - HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)}; + auto hr = helper.openDevice(devid, eCapture, mmdev); if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); + WARN("Failed to open device \"{}\": {:#x}", devname.empty() + ? "(default)"sv : std::string_view{devname}, as_unsigned(hr)); return hr; } - mClient = nullptr; if(!devname.empty()) - mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + mDeviceName = std::move(devname); else - mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; + mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } -void WasapiCapture::closeProxy() +auto WasapiCapture::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, + ComPtr &client, ComPtr &capture) -> HRESULT { - mClient = nullptr; - mMMDev = nullptr; -} + capture = nullptr; + client = nullptr; -HRESULT WasapiCapture::resetProxy() -{ - mClient = nullptr; - - HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), - al::out_ptr(mClient))}; + auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(client)); if(FAILED(hr)) { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); + auto wfx = unique_coptr{}; + hr = client->GetMixFormat(al::out_ptr(wfx)); if(FAILED(hr)) { - ERR("Failed to get capture format: 0x%08lx\n", hr); + ERR("Failed to get capture format: {:#x}", as_unsigned(hr)); return hr; } - TraceFormat("Device capture format", wfx); + TraceFormat("Device capture format", wfx.get()); - WAVEFORMATEXTENSIBLE InputType{}; - if(!MakeExtensible(&InputType, wfx)) - { - CoTaskMemFree(wfx); + auto InputType = WAVEFORMATEXTENSIBLE{}; + if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; const bool isRear51{InputType.Format.nChannels == 6 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR}; // Make sure buffer is at least 100ms in size - ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + ReferenceTime buf_time{ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate}; buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); InputType = {}; @@ -2373,6 +2785,7 @@ HRESULT WasapiCapture::resetProxy() InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: return E_FAIL; @@ -2400,8 +2813,9 @@ HRESULT WasapiCapture::resetProxy() InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; - InputType.Format.nSamplesPerSec = mDevice->Frequency; + InputType.Format.nSamplesPerSec = mDevice->mSampleRate; InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); @@ -2410,15 +2824,15 @@ HRESULT WasapiCapture::resetProxy() InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); TraceFormat("Requesting capture format", &InputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); + hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, al::out_ptr(wfx)); if(FAILED(hr)) { - WARN("Failed to check capture format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + 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: 0x%08lx\n", hr); + ERR("Failed to find a supported capture format: {:#x}", as_unsigned(hr)); return hr; } @@ -2427,13 +2841,9 @@ HRESULT WasapiCapture::resetProxy() if(wfx != nullptr) { - TraceFormat("Got capture format", wfx); - if(!MakeExtensible(&InputType, wfx)) - { - CoTaskMemFree(wfx); + TraceFormat("Got capture format", wfx.get()); + if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; - } - CoTaskMemFree(wfx); wfx = nullptr; auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept @@ -2462,6 +2872,8 @@ HRESULT WasapiCapture::resetProxy() 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()); } @@ -2469,9 +2881,9 @@ HRESULT WasapiCapture::resetProxy() }; if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask)) { - ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n", + ERR("Failed to match format, wanted: {} {} {}hz, got: {:#08x} mask {} channel{} {}-bit {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels, + mDevice->mSampleRate, InputType.dwChannelMask, InputType.Format.nChannels, (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample, InputType.Format.nSamplesPerSec); return E_FAIL; @@ -2489,7 +2901,7 @@ HRESULT WasapiCapture::resetProxy() srcType = DevFmtInt; else { - ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample); + ERR("Unhandled integer bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } @@ -2499,13 +2911,13 @@ HRESULT WasapiCapture::resetProxy() srcType = DevFmtFloat; else { - ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample); + ERR("Unhandled float bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } else { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str()); + ERR("Unhandled format sub-type: {}", GuidPrinter{InputType.SubFormat}.str()); return E_FAIL; } @@ -2522,7 +2934,7 @@ HRESULT WasapiCapture::resetProxy() mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask, mDevice->FmtChans}; - TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType)); + TRACE("Created {} multichannel-to-mono converter", DevFmtTypeString(srcType)); /* The channel converter always outputs float, so change the input type * for the resampler/type-converter. */ @@ -2531,54 +2943,61 @@ HRESULT WasapiCapture::resetProxy() else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1) { mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans}; - TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType)); + TRACE("Created {} mono-to-stereo converter", DevFmtTypeString(srcType)); srcType = DevFmtFloat; } - if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) + if(mDevice->mSampleRate != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType, - mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency, + mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->mSampleRate, Resampler::FastBSinc24); if(!mSampleConv) { - ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", + ERR("Failed to create converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); + mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); return E_FAIL; } - TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n", + TRACE("Created converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), - mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); + mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); } - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 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: 0x%08lx\n", hr); + ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); + return hr; + } + + hr = client->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(capture)); + if(FAILED(hr)) + { + ERR("Failed to get IAudioCaptureClient: {:#x}", as_unsigned(hr)); return hr; } UINT32 buffer_len{}; ReferenceTime min_per{}; - hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + hr = client->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); + hr = client->GetBufferSize(&buffer_len); if(FAILED(hr)) { - ERR("Failed to get buffer size: 0x%08lx\n", hr); + ERR("Failed to get buffer size: {:#x}", as_unsigned(hr)); return hr; } - mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency); - mDevice->BufferSize = buffer_len; + mDevice->mUpdateSize = RefTime2Samples(min_per, mDevice->mSampleRate); + mDevice->mBufferSize = buffer_len; mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false); - hr = mClient->SetEventHandle(mNotifyEvent); + hr = client->SetEventHandle(mNotifyEvent); if(FAILED(hr)) { - ERR("Failed to set event handle: 0x%08lx\n", hr); + ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } @@ -2588,61 +3007,28 @@ HRESULT WasapiCapture::resetProxy() void WasapiCapture::start() { - const HRESULT hr{pushMessage(MsgType::StartDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start recording: 0x%lx", hr}; + 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)}; } -HRESULT WasapiCapture::startProxy() -{ - ResetEvent(mNotifyEvent); - - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) - { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } - - hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture)); - if(SUCCEEDED(hr)) - { - try { - mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; - } - catch(...) { - mCapture = nullptr; - ERR("Failed to start thread\n"); - hr = E_FAIL; - } - } - - if(FAILED(hr)) - { - mClient->Stop(); - mClient->Reset(); - } - - return hr; -} - - void WasapiCapture::stop() -{ pushMessage(MsgType::StopDevice).wait(); } - -void WasapiCapture::stopProxy() { - if(!mCapture || !mThread.joinable()) - return; - - mKillNow.store(true, std::memory_order_release); - mThread.join(); - - mCapture = nullptr; - mClient->Stop(); - mClient->Reset(); + auto plock = std::unique_lock{mProcMutex}; + if(mState == ThreadState::Recording) + { + mKillNow = true; + mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Recording; }); + } } @@ -2663,7 +3049,7 @@ bool WasapiBackendFactory::init() std::promise promise; auto future = promise.get_future(); - std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + std::thread{&DeviceEnumHelper::messageHandler, &promise}.detach(); InitResult = future.get(); } catch(...) { @@ -2675,9 +3061,9 @@ bool WasapiBackendFactory::init() bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WasapiBackendFactory::probe(BackendType type) +auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto devlock = DeviceListLock{gDeviceList}; switch(type) @@ -2685,20 +3071,19 @@ std::string WasapiBackendFactory::probe(BackendType type) case BackendType::Playback: { auto defaultId = devlock.getPlaybackDefaultId(); - for(const DevMap &entry : devlock.getPlaybackList()) + auto &devlist = devlock.getPlaybackList(); + + outnames.reserve(devlist.size()); + std::transform(devlist.cbegin(), devlist.cend(), std::back_inserter(outnames), + std::mem_fn(&DevMap::name)); + + /* Default device goes first. */ + const auto defiter = std::find_if(devlist.cbegin(), devlist.cend(), + [defaultId](const DevMap &entry) -> bool { return entry.devid == defaultId; }); + if(defiter != devlist.cend()) { - if(entry.devid != defaultId) - { - /* +1 to also append the null char (to ensure a null- - * separated list and double-null terminated list). - */ - outnames.append(GetDevicePrefix()).append(entry.name.c_str(), - entry.name.length()+1); - continue; - } - /* Default device goes first. */ - std::string name{std::string{GetDevicePrefix()} + entry.name}; - outnames.insert(0, name.c_str(), name.length()+1); + const auto defname = outnames.begin() + std::distance(devlist.cbegin(), defiter); + std::rotate(outnames.begin(), defname, defname+1); } } break; @@ -2706,16 +3091,19 @@ std::string WasapiBackendFactory::probe(BackendType type) case BackendType::Capture: { auto defaultId = devlock.getCaptureDefaultId(); - for(const DevMap &entry : devlock.getCaptureList()) + auto &devlist = devlock.getCaptureList(); + + outnames.reserve(devlist.size()); + std::transform(devlist.cbegin(), devlist.cend(), std::back_inserter(outnames), + std::mem_fn(&DevMap::name)); + + /* Default device goes first. */ + const auto defiter = std::find_if(devlist.cbegin(), devlist.cend(), + [defaultId](const DevMap &entry) -> bool { return entry.devid == defaultId; }); + if(defiter != devlist.cend()) { - if(entry.devid != defaultId) - { - outnames.append(GetDevicePrefix()).append(entry.name.c_str(), - entry.name.length()+1); - continue; - } - std::string name{std::string{GetDevicePrefix()} + entry.name}; - outnames.insert(0, name.c_str(), name.length()+1); + const auto defname = outnames.begin() + std::distance(devlist.cbegin(), defiter); + std::rotate(outnames.begin(), defname, defname+1); } } break; @@ -2748,7 +3136,7 @@ alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventTy case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP return alc::EventSupport::FullSupport; #endif diff --git a/libs/openal-soft/alc/backends/wasapi.h b/libs/openal-soft/alc/backends/wasapi.h index 12fd95ef..f3cb8541 100644 --- a/libs/openal-soft/alc/backends/wasapi.h +++ b/libs/openal-soft/alc/backends/wasapi.h @@ -5,17 +5,17 @@ struct WasapiBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WASAPI_H */ diff --git a/libs/openal-soft/alc/backends/wave.cpp b/libs/openal-soft/alc/backends/wave.cpp index f18c80ac..153e6145 100644 --- a/libs/openal-soft/alc/backends/wave.cpp +++ b/libs/openal-soft/alc/backends/wave.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -39,12 +38,9 @@ #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" -#include "opthelpers.h" #include "strutils.h" @@ -99,7 +95,7 @@ void fwrite32le(uint val, FILE *f) struct WaveBackend final : public BackendBase { - WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); @@ -122,7 +118,7 @@ WaveBackend::~WaveBackend() = default; int WaveBackend::mixerProc() { - const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; + const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; althrd_setname(GetMixerThreadName()); @@ -137,17 +133,17 @@ int WaveBackend::mixerProc() auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ - int64_t avail{std::chrono::duration_cast((now-start) * - mDevice->Frequency).count()}; - if(avail-done < mDevice->UpdateSize) + const auto avail = int64_t{std::chrono::duration_cast((now-start) * + mDevice->mSampleRate).count()}; + if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } - while(avail-done >= mDevice->UpdateSize) + while(avail-done >= mDevice->mUpdateSize) { - mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep); - done += mDevice->UpdateSize; + mDevice->renderSamples(mBuffer.data(), mDevice->mUpdateSize, frameStep); + done += mDevice->mUpdateSize; if(al::endian::native != al::endian::little) { @@ -170,10 +166,10 @@ int WaveBackend::mixerProc() } } - const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())}; - if(fs < mDevice->UpdateSize || ferror(mFile.get())) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->mUpdateSize, mFile.get())}; + if(fs < mDevice->mUpdateSize || ferror(mFile.get())) { - ERR("Error writing to file\n"); + ERR("Error writing to file"); mDevice->handleDisconnect("Failed to write playback samples"); break; } @@ -184,10 +180,10 @@ int WaveBackend::mixerProc() * and current time from growing too large, while maintaining the * correct number of samples to render. */ - if(done >= mDevice->Frequency) + if(done >= mDevice->mSampleRate) { - seconds s{done/mDevice->Frequency}; - done %= mDevice->Frequency; + seconds s{done/mDevice->mSampleRate}; + done %= mDevice->mSampleRate; start += s; } } @@ -204,8 +200,8 @@ void WaveBackend::open(std::string_view name) if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; @@ -219,20 +215,14 @@ void WaveBackend::open(std::string_view name) mFile = FilePtr{fopen(fname->c_str(), "wb")}; #endif if(!mFile) - throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", - fname->c_str(), std::generic_category().message(errno).c_str()}; + throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}", + *fname, std::generic_category().message(errno)}; - mDevice->DeviceName = name; + mDeviceName = name; } bool WaveBackend::reset() { - uint channels{0}, bytes{0}, chanmask{0}; - bool isbformat{false}; - - fseek(mFile.get(), 0, SEEK_SET); - clearerr(mFile.get()); - if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; @@ -256,6 +246,8 @@ bool WaveBackend::reset() case DevFmtFloat: break; } + auto chanmask = 0u; + auto isbformat = false; switch(mDevice->FmtChans) { case DevFmtMono: chanmask = 0x04; break; @@ -264,6 +256,9 @@ bool WaveBackend::reset() case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + [[fallthrough]]; case DevFmtX714: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 | 0x8000 | 0x20000; @@ -279,13 +274,24 @@ bool WaveBackend::reset() chanmask = 0; break; } - bytes = mDevice->bytesFromFmt(); - channels = mDevice->channelsFromFmt(); + const auto bytes = mDevice->bytesFromFmt(); + const auto channels = mDevice->channelsFromFmt(); - rewind(mFile.get()); + if(fseek(mFile.get(), 0, SEEK_CUR) != 0) + { + /* ESPIPE means the underlying file isn't seekable, which is fine for + * piped output. + */ + if(auto errcode = errno; errcode != ESPIPE) + { + ERR("Failed to reset file offset: {} ({})", std::generic_category().message(errcode), + errcode); + } + } + clearerr(mFile.get()); fputs("RIFF", mFile.get()); - fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close + fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at stop fputs("WAVE", mFile.get()); @@ -297,9 +303,9 @@ bool WaveBackend::reset() // 16-bit val, channel count fwrite16le(static_cast(channels), mFile.get()); // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile.get()); + fwrite32le(mDevice->mSampleRate, mFile.get()); // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile.get()); + fwrite32le(mDevice->mSampleRate * channels * bytes, mFile.get()); // 16-bit val, frame size fwrite16le(static_cast(channels * bytes), mFile.get()); // 16-bit val, bits per sample @@ -316,18 +322,18 @@ bool WaveBackend::reset() (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); fputs("data", mFile.get()); - fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close + fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at stop if(ferror(mFile.get())) { - ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str()); + ERR("Error writing header: {}", std::generic_category().message(errno)); return false; } mDataStart = ftell(mFile.get()); setDefaultWFXChannelOrder(); - const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize}; + const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->mUpdateSize}; mBuffer.resize(bufsize); return true; @@ -336,14 +342,14 @@ bool WaveBackend::reset() void WaveBackend::start() { if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) - WARN("Failed to seek on output file\n"); + WARN("Failed to seek on output file"); try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this}; + mThread = std::thread{&WaveBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -376,17 +382,16 @@ bool WaveBackendFactory::init() bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string WaveBackendFactory::probe(BackendType type) +auto WaveBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: - /* Include null char. */ - return std::string{GetDeviceName()} + '\0'; + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return std::string{}; + return {}; } BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/wave.h b/libs/openal-soft/alc/backends/wave.h index e768d336..85f4c76f 100644 --- a/libs/openal-soft/alc/backends/wave.h +++ b/libs/openal-soft/alc/backends/wave.h @@ -5,15 +5,15 @@ struct WaveBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WAVE_H */ diff --git a/libs/openal-soft/alc/backends/winmm.cpp b/libs/openal-soft/alc/backends/winmm.cpp index 16126027..349a244d 100644 --- a/libs/openal-soft/alc/backends/winmm.cpp +++ b/libs/openal-soft/alc/backends/winmm.cpp @@ -36,15 +36,14 @@ #include #include #include -#include #include "alnumeric.h" #include "alsem.h" -#include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" +#include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" #include "vector.h" @@ -55,9 +54,6 @@ namespace { -#define DEVNAME_HEAD "OpenAL Soft on " - - std::vector PlaybackDevices; std::vector CaptureDevices; @@ -77,19 +73,15 @@ void ProbePlaybackDevices() WAVEOUTCAPSW WaveCaps{}; if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(PlaybackDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); + TRACE("Got device \"{}\", ID {}", dname, i); } PlaybackDevices.emplace_back(std::move(dname)); } @@ -108,19 +100,15 @@ void ProbeCaptureDevices() WAVEINCAPSW WaveCaps{}; if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); - int count{1}; - std::string newname{basename}; + auto count = 1; + auto newname = basename; while(checkName(CaptureDevices, newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } + newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); - TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i); + TRACE("Got device \"{}\", ID {}", dname, i); } CaptureDevices.emplace_back(std::move(dname)); } @@ -128,7 +116,7 @@ void ProbeCaptureDevices() struct WinMMPlayback final : public BackendBase { - WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMPlayback() override; void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -195,7 +183,7 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() WAVEHDR &waveHdr = mWaveBuffer[widx]; if(++widx == mWaveBuffer.size()) widx = 0; - mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels); + mDevice->renderSamples(waveHdr.lpData, mDevice->mUpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -216,61 +204,57 @@ void WinMMPlayback::open(std::string_view name) std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; -retry_open: WAVEFORMATEX format{}; - if(fmttype == DevFmtFloat) - { - format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - format.wBitsPerSample = 32; - } - else - { - format.wFormatTag = WAVE_FORMAT_PCM; - if(fmttype == DevFmtUByte || fmttype == DevFmtByte) - format.wBitsPerSample = 8; - else - format.wBitsPerSample = 16; - } - format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); - format.nSamplesPerSec = mDevice->Frequency; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.cbSize = 0; - - HWAVEOUT outHandle{}; - MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, - reinterpret_cast(&WinMMPlayback::waveOutProcC), - reinterpret_cast(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { + do { + format = WAVEFORMATEX{}; if(fmttype == DevFmtFloat) { - fmttype = DevFmtShort; - goto retry_open; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } - throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; - } + else + { + format.wFormatTag = WAVE_FORMAT_PCM; + if(fmttype == DevFmtUByte || fmttype == DevFmtByte) + format.wBitsPerSample = 8; + else + format.wBitsPerSample = 16; + } + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->mSampleRate; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format, + reinterpret_cast(&WinMMPlayback::waveOutProcC), + reinterpret_cast(this), CALLBACK_FUNCTION)}; + if(res == MMSYSERR_NOERROR) break; + + if(fmttype != DevFmtFloat) + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}", + res}; + + fmttype = DevFmtShort; + } while(true); - if(mOutHdl) - waveOutClose(mOutHdl); - mOutHdl = outHandle; mFormat = format; - mDevice->DeviceName = PlaybackDevices[DeviceID]; + mDeviceName = PlaybackDevices[DeviceID]; } bool WinMMPlayback::reset() { - mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * - mFormat.nSamplesPerSec / mDevice->Frequency); - mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u; - mDevice->UpdateSize = mDevice->BufferSize / 4; - mDevice->Frequency = mFormat.nSamplesPerSec; + mDevice->mBufferSize = static_cast(uint64_t{mDevice->mBufferSize} * + mFormat.nSamplesPerSec / mDevice->mSampleRate); + mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3u; + mDevice->mUpdateSize = mDevice->mBufferSize / 4; + mDevice->mSampleRate = mFormat.nSamplesPerSec; if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { @@ -278,7 +262,7 @@ bool WinMMPlayback::reset() mDevice->FmtType = DevFmtFloat; else { - ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample); + ERR("Unhandled IEEE float sample depth: {}", mFormat.wBitsPerSample); return false; } } @@ -290,13 +274,13 @@ bool WinMMPlayback::reset() mDevice->FmtType = DevFmtUByte; else { - ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample); + ERR("Unhandled PCM sample depth: {}", mFormat.wBitsPerSample); return false; } } else { - ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag); + ERR("Unhandled format tag: {:#04x}", as_unsigned(mFormat.wFormatTag)); return false; } @@ -306,12 +290,12 @@ bool WinMMPlayback::reset() mDevice->FmtChans = DevFmtMono; else { - ERR("Unhandled channel count: %d\n", mFormat.nChannels); + ERR("Unhandled channel count: {}", mFormat.nChannels); return false; } setDefaultWFXChannelOrder(); - const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; + const uint BufferSize{mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; @@ -336,11 +320,11 @@ void WinMMPlayback::start() mWritable.store(static_cast(mWaveBuffer.size()), std::memory_order_release); mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this}; + mThread = std::thread{&WinMMPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, - "Failed to start mixing thread: %s", e.what()}; + "Failed to start mixing thread: {}", e.what()}; } } @@ -359,7 +343,7 @@ void WinMMPlayback::stop() struct WinMMCapture final : public BackendBase { - WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } + explicit WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; @@ -451,8 +435,8 @@ void WinMMCapture::open(std::string_view name) std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", - al::sizei(name), name.data()}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", + name}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -466,9 +450,10 @@ void WinMMCapture::open(std::string_view name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: - throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } @@ -483,7 +468,7 @@ void WinMMCapture::open(std::string_view name) case DevFmtByte: case DevFmtUShort: case DevFmtUInt: - throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported", + throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } @@ -493,7 +478,7 @@ void WinMMCapture::open(std::string_view name) mFormat.nChannels = static_cast(mDevice->channelsFromFmt()); mFormat.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); mFormat.nBlockAlign = static_cast(mFormat.wBitsPerSample * mFormat.nChannels / 8); - mFormat.nSamplesPerSec = mDevice->Frequency; + mFormat.nSamplesPerSec = mDevice->mSampleRate; mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.cbSize = 0; @@ -501,7 +486,7 @@ void WinMMCapture::open(std::string_view name) reinterpret_cast(&WinMMCapture::waveInProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) - throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res}; + throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: {}", res}; // Ensure each buffer is 50ms each DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; @@ -509,7 +494,7 @@ void WinMMCapture::open(std::string_view name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - const auto CapturedDataSize = std::max(mDevice->BufferSize, + const auto CapturedDataSize = std::max(mDevice->mBufferSize, BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); @@ -525,7 +510,7 @@ void WinMMCapture::open(std::string_view name) mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; } - mDevice->DeviceName = CaptureDevices[DeviceID]; + mDeviceName = CaptureDevices[DeviceID]; } void WinMMCapture::start() @@ -538,13 +523,13 @@ void WinMMCapture::start() } mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this}; + 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: %s", e.what()}; + "Failed to start recording thread: {}", e.what()}; } } @@ -582,26 +567,23 @@ bool WinMMBackendFactory::init() bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WinMMBackendFactory::probe(BackendType type) +auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const std::string &dname) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - if(!dname.empty()) - outnames.append(dname.c_str(), dname.length()+1); - }; + { if(!dname.empty()) outnames.emplace_back(dname); }; + switch(type) { case BackendType::Playback: ProbePlaybackDevices(); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: ProbeCaptureDevices(); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/libs/openal-soft/alc/backends/winmm.h b/libs/openal-soft/alc/backends/winmm.h index 45a706aa..51ce432c 100644 --- a/libs/openal-soft/alc/backends/winmm.h +++ b/libs/openal-soft/alc/backends/winmm.h @@ -5,15 +5,15 @@ struct WinMMBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WINMM_H */ diff --git a/libs/openal-soft/alc/context.cpp b/libs/openal-soft/alc/context.cpp index 1b58cffd..3e1c5f87 100644 --- a/libs/openal-soft/alc/context.cpp +++ b/libs/openal-soft/alc/context.cpp @@ -6,12 +6,12 @@ #include #include #include -#include #include -#include +#include #include -#include +#include #include +#include #include #include "AL/efx.h" @@ -25,20 +25,22 @@ #include "albit.h" #include "alc/alu.h" #include "alc/backends/base.h" +#include "alnumeric.h" #include "alspan.h" +#include "atomic.h" #include "core/async_event.h" +#include "core/devformat.h" #include "core/device.h" #include "core/effectslot.h" #include "core/logging.h" -#include "core/voice.h" #include "core/voice_change.h" #include "device.h" #include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" -#ifdef ALSOFT_EAX -#include "alstring.h" +#if ALSOFT_EAX +#include "al/eax/call.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX @@ -71,7 +73,7 @@ std::vector getContextExtensions() noexcept "AL_EXT_STEREO_ANGLES"sv, "AL_LOKI_quadriphonic"sv, "AL_SOFT_bformat_ex"sv, - "AL_SOFTX_bformat_hoa"sv, + "AL_SOFT_bformat_hoa"sv, "AL_SOFT_block_alignment"sv, "AL_SOFT_buffer_length_query"sv, "AL_SOFT_callback_buffer"sv, @@ -108,7 +110,7 @@ ALCcontext::ThreadCtx::~ThreadCtx() if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) { const bool result{ctx->releaseIfNoDelete()}; - ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, + ERR("Context {} current for thread being destroyed{}!", voidp{ctx}, result ? "" : ", leak detected"); } } @@ -117,26 +119,30 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; -ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) +ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} { mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); + + /* Low-severity debug messages are disabled by default. */ + alDebugMessageControlDirectEXT(this, AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, + AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_FALSE); } ALCcontext::~ALCcontext() { - TRACE("Freeing context %p\n", voidp{this}); + TRACE("Freeing context {}", voidp{this}); size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) - WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Source{} not deleted", count, (count==1)?"":"s"); mSourceList.clear(); mNumSources = 0; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eaxUninitialize(); #endif // ALSOFT_EAX @@ -145,7 +151,7 @@ ALCcontext::~ALCcontext() [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s"); mEffectSlotList.clear(); mNumEffectSlots = 0; } @@ -163,9 +169,9 @@ void ALCcontext::init() auxslots = EffectSlot::CreatePtrArray(0); else { - auxslots = EffectSlot::CreatePtrArray(1); + auxslots = EffectSlot::CreatePtrArray(2); (*auxslots)[0] = mDefaultSlot->mSlot; - std::uninitialized_fill_n(al::to_address(auxslots->end()), 1_uz, nullptr); + (*auxslots)[1] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); @@ -182,15 +188,17 @@ void ALCcontext::init() if(sBufferSubDataCompat) { - auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"); + auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); if(iter != mExtensions.end()) mExtensions.erase(iter); - /* TODO: Would be nice to sort this alphabetically. Needs case- - * insensitive searching. + + /* Insert the AL_SOFT_buffer_sub_data extension string between + * AL_SOFT_buffer_length_query and AL_SOFT_callback_buffer. */ - mExtensions.emplace_back("AL_SOFT_buffer_sub_data"); + iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_SOFT_callback_buffer"sv); + mExtensions.emplace(iter, "AL_SOFT_buffer_sub_data"sv); } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX @@ -213,19 +221,31 @@ void ALCcontext::init() mExtensionsString = std::move(extensions); } +#if ALSOFT_EAX + eax_set_defaults(); +#endif + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; mParams.Gain = mListener.Gain; - mParams.MetersPerUnit = mListener.mMetersPerUnit; + mParams.MetersPerUnit = mListener.mMetersPerUnit +#if ALSOFT_EAX + * eaxGetDistanceFactor() +#endif + ; mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF; mParams.DopplerFactor = mDopplerFactor; - mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity; + mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity +#if ALSOFT_EAX + / eaxGetDistanceFactor() +#endif + ; mParams.SourceDistanceModel = mSourceDistanceModel; mParams.mDistanceModel = mDistanceModel; - mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false); + mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false); StartEventThrd(this); @@ -237,35 +257,34 @@ void ALCcontext::deinit() { if(sLocalContext == this) { - WARN("%p released while current on thread\n", voidp{this}); + WARN("{} released while current on thread", voidp{this}); + auto _ = ContextRef{sLocalContext}; sThreadContext.set(nullptr); - dec_ref(); } - ALCcontext *origctx{this}; - if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) + if(ALCcontext *origctx{this}; sGlobalContext.compare_exchange_strong(origctx, nullptr)) { + auto _ = ContextRef{origctx}; while(sGlobalContextLock.load()) { /* Wait to make sure another thread didn't get the context and is * trying to increment its refcount. */ } - dec_ref(); } bool stopPlayback{}; /* First make sure this context exists in the device's list. */ - auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); - if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) + auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)}; + if(auto toremove = static_cast(std::count(oldarray.begin(), oldarray.end(), this))) { using ContextArray = al::FlexArray; - const size_t newsize{oldarray->size() - toremove}; + const auto newsize = size_t{oldarray.size() - toremove}; auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. */ - std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), + std::copy_if(oldarray.begin(), oldarray.end(), newarray->begin(), [this](ContextBase *ctx) { return ctx != this; }); /* Store the new context array in the device. Wait for any current mix @@ -277,7 +296,7 @@ void ALCcontext::deinit() stopPlayback = (newsize == 0); } else - stopPlayback = oldarray->empty(); + stopPlayback = oldarray.empty(); StopEventThrd(this); @@ -298,7 +317,7 @@ void ALCcontext::applyAllUpdates() /* busy-wait */ } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX if(mEaxNeedsCommit) eaxCommit(); #endif @@ -315,7 +334,7 @@ void ALCcontext::applyAllUpdates() } -#ifdef ALSOFT_EAX +#if ALSOFT_EAX namespace { template @@ -493,7 +512,7 @@ void ALCcontext::eax_initialize() eax_ensure_compatibility(); eax_set_defaults(); - eax_context_commit_air_absorbtion_hf(); + eax_context_commit_air_absorption_hf(); eax_update_speaker_configuration(); eax_initialize_fx_slots(); @@ -547,10 +566,11 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const case DevFmtX51: return SPEAKERS_5; case DevFmtX61: return SPEAKERS_6; case DevFmtX71: return SPEAKERS_7; - /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to + /* 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). */ @@ -561,7 +581,8 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const */ case DevFmtAmbi3D: return SPEAKERS_7; } - ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans); + ERR(EAX_PREFIX "Unexpected device channel format {:#x}.", + uint{al::to_underlying(mDevice->FmtChans)}); return HEADPHONES; #undef EAX_PREFIX @@ -619,7 +640,7 @@ void ALCcontext::eax_context_set_defaults() eax5_context_set_defaults(mEax5); mEax = mEax5.i; mEaxVersion = 5; - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } void ALCcontext::eax_set_defaults() @@ -746,14 +767,11 @@ void ALCcontext::eax_context_commit_primary_fx_slot_id() void ALCcontext::eax_context_commit_distance_factor() { - if(mListener.mMetersPerUnit == mEax.flDistanceFactor) - return; - - mListener.mMetersPerUnit = mEax.flDistanceFactor; + /* mEax.flDistanceFactor was changed, so the context props are dirty. */ mPropsDirty = true; } -void ALCcontext::eax_context_commit_air_absorbtion_hf() +void ALCcontext::eax_context_commit_air_absorption_hf() { const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); @@ -814,16 +832,16 @@ void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) - mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) - mEaxDf |= eax_distance_factor_dirty_bit; + mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) - mEaxDf |= eax_air_absorption_hf_dirty_bit; + mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) - mEaxDf |= eax_hf_reference_dirty_bit; + mEaxDf.set(eax_hf_reference_dirty_bit); } void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) @@ -834,20 +852,20 @@ void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) eax4_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: - eax_defer( - call, state, &EAX40CONTEXTPROPERTIES::flHFReference); + eax_defer(call, state, + &EAX40CONTEXTPROPERTIES::flHFReference); break; default: eax_set_misc(call); @@ -864,19 +882,19 @@ void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) - mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) - mEaxDf |= eax_distance_factor_dirty_bit; + mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) - mEaxDf |= eax_air_absorption_hf_dirty_bit; + mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) - mEaxDf |= eax_hf_reference_dirty_bit; + mEaxDf.set(eax_hf_reference_dirty_bit); if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) - mEaxDf |= eax_macro_fx_factor_dirty_bit; + mEaxDf.set(eax_macro_fx_factor_dirty_bit); } void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) @@ -887,24 +905,24 @@ void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) eax5_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flHFReference); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: - eax_defer( - call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + eax_defer(call, state, + &EAX50CONTEXTPROPERTIES::flMacroFXFactor); break; default: eax_set_misc(call); @@ -922,49 +940,49 @@ void ALCcontext::eax_set(const EaxCall& call) default: eax_fail_unknown_version(); } if(version != mEaxVersion) - mEaxDf = ~EaxDirtyFlags(); + mEaxDf.set(); mEaxVersion = version; } -void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) +void ALCcontext::eax4_context_commit(Eax4State& state, std::bitset& dst_df) { - if(mEaxDf == EaxDirtyFlags{}) + if(mEaxDf.none()) return; - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); - eax_context_commit_property( - state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property(state, dst_df, + &EAX40CONTEXTPROPERTIES::flHFReference); - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } -void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) +void ALCcontext::eax5_context_commit(Eax5State& state, std::bitset& dst_df) { - if(mEaxDf == EaxDirtyFlags{}) + if(mEaxDf.none()) return; - eax_context_commit_property( - state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); - eax_context_commit_property( - state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor); - eax_context_commit_property( - state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); - eax_context_commit_property( - state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference); - eax_context_commit_property( - state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property(state, dst_df, + &EAX50CONTEXTPROPERTIES::flMacroFXFactor); - mEaxDf = EaxDirtyFlags{}; + mEaxDf.reset(); } void ALCcontext::eax_context_commit() { - auto dst_df = EaxDirtyFlags{}; + auto dst_df = std::bitset{}; switch(mEaxVersion) { @@ -981,25 +999,25 @@ void ALCcontext::eax_context_commit() break; } - if(dst_df == EaxDirtyFlags{}) + if(dst_df.none()) return; - if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_context_commit_primary_fx_slot_id(); - if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_distance_factor_dirty_bit)) eax_context_commit_distance_factor(); - if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) - eax_context_commit_air_absorbtion_hf(); + if(dst_df.test(eax_air_absorption_hf_dirty_bit)) + eax_context_commit_air_absorption_hf(); - if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_hf_reference_dirty_bit)) eax_context_commit_hf_reference(); - if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_macro_fx_factor_dirty_bit)) eax_context_commit_macro_fx_factor(); - if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_update_sources(); } diff --git a/libs/openal-soft/alc/context.h b/libs/openal-soft/alc/context.h index 10afd4ab..3791e0b6 100644 --- a/libs/openal-soft/alc/context.h +++ b/libs/openal-soft/alc/context.h @@ -1,12 +1,14 @@ #ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H -#include +#include "config.h" + #include +#include +#include #include #include #include -#include #include #include #include @@ -18,32 +20,31 @@ #include "AL/alext.h" #include "al/listener.h" -#include "almalloc.h" -#include "alnumeric.h" #include "althreads.h" -#include "atomic.h" #include "core/context.h" -#include "inprogext.h" +#include "fmt/core.h" #include "intrusive_ptr.h" +#include "opthelpers.h" -#ifdef ALSOFT_EAX -#include "al/eax/call.h" +#if ALSOFT_EAX +#include "al/eax/api.h" #include "al/eax/exception.h" #include "al/eax/fx_slot_index.h" #include "al/eax/fx_slots.h" #include "al/eax/utils.h" + +class EaxCall; #endif // ALSOFT_EAX struct ALeffect; struct ALeffectslot; -struct ALsource; struct DebugGroup; struct EffectSlotSubList; struct SourceSubList; -enum class DebugSource : uint8_t; -enum class DebugType : uint8_t; -enum class DebugSeverity : uint8_t; +enum class DebugSource : std::uint8_t; +enum class DebugType : std::uint8_t; +enum class DebugSeverity : std::uint8_t; using uint = unsigned int; @@ -72,9 +73,12 @@ struct DebugLogEntry { }; -struct ALCcontext : public al::intrusive_ref, ContextBase { - const al::intrusive_ptr mALDevice; +namespace al { +struct Device; +} // namespace al +struct ALCcontext final : public al::intrusive_ref, ContextBase { + const al::intrusive_ptr mALDevice; bool mPropsDirty{true}; bool mDeferUpdates{false}; @@ -118,15 +122,15 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { std::unique_ptr mDefaultSlot; std::vector mExtensions; - std::string mExtensionsString{}; + std::string mExtensionsString; std::unordered_map mSourceNames; std::unordered_map mEffectSlotNames; - ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); + ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); ALCcontext(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete; - ~ALCcontext(); + ~ALCcontext() final; void init(); /** @@ -159,12 +163,18 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { */ void applyAllUpdates(); -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - void setError(ALenum errorCode, const char *msg, ...); + void setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); + + template + void setError(ALenum errorCode, fmt::format_string msg, Args&& ...args) + { setErrorImpl(errorCode, msg, fmt::make_format_args(args...)); } + + [[noreturn]] + void throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); + + template [[noreturn]] + void throw_error(ALenum errorCode, fmt::format_string fmt, Args&&... args) + { throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); } void sendDebugMessage(std::unique_lock &debuglock, DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message); @@ -191,6 +201,10 @@ private: */ 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 @@ -210,7 +224,7 @@ public: /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX bool hasEax() const noexcept { return mEaxIsInitialized; } bool eaxIsCapable() const noexcept; @@ -232,7 +246,11 @@ public: void eaxSetLastError() noexcept; - EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept + [[nodiscard]] + auto eaxGetDistanceFactor() const noexcept -> float { return mEax.flDistanceFactor; } + + [[nodiscard]] + auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex { return mEaxPrimaryFxSlotIndex; } const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const @@ -247,11 +265,14 @@ public: { mEaxFxSlots.commit(); } private: - static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0; - static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1; - static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2; - static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3; - static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4; + 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; @@ -267,12 +288,11 @@ private: Eax5Props d; // Deferred. }; - class ContextException : public EaxException - { + class ContextException final : public EaxException { public: - explicit ContextException(const char* message) + explicit ContextException(const char *message) : EaxException{"EAX_CONTEXT", message} - {} + { } }; struct Eax4PrimaryFxSlotIdValidator { @@ -420,7 +440,7 @@ private: int mEaxVersion{}; // Current EAX version. bool mEaxNeedsCommit{}; - EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. + std::bitset mEaxDf; // Dirty flags for the current EAX version. Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. Eax4State mEax4{}; // EAX4 state. Eax5State mEax5{}; // EAX5 state. @@ -450,7 +470,7 @@ private: // updates a dirty flag. template< typename TValidator, - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> @@ -463,20 +483,20 @@ private: dst_d = src; if(dst_i != dst_d) - mEaxDf |= TDirtyBit; + mEaxDf.set(DirtyBit); } template< - EaxDirtyFlags TDirtyBit, + size_t DirtyBit, typename TMemberResult, typename TProps, typename TState> - void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, + void eax_context_commit_property(TState& state, std::bitset& dst_df, TMemberResult TProps::*member) noexcept { - if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) + if(mEaxDf.test(DirtyBit)) { - dst_df |= TDirtyBit; + dst_df.set(DirtyBit); const auto& src_d = state.d.*member; state.i.*member = src_d; mEax.*member = src_d; @@ -514,7 +534,7 @@ private: void eax_context_commit_primary_fx_slot_id(); void eax_context_commit_distance_factor(); - void eax_context_commit_air_absorbtion_hf(); + void eax_context_commit_air_absorption_hf(); void eax_context_commit_hf_reference(); void eax_context_commit_macro_fx_factor(); @@ -529,8 +549,8 @@ private: void eax5_defer(const EaxCall& call, Eax5State& state); void eax_set(const EaxCall& call); - void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); - void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); + void 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 }; @@ -545,7 +565,7 @@ void UpdateContextProps(ALCcontext *context); inline bool TrapALError{false}; -#ifdef ALSOFT_EAX +#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; diff --git a/libs/openal-soft/alc/device.cpp b/libs/openal-soft/alc/device.cpp index fc145579..ad607160 100644 --- a/libs/openal-soft/alc/device.cpp +++ b/libs/openal-soft/alc/device.cpp @@ -3,6 +3,7 @@ #include "device.h" +#include #include #include @@ -10,15 +11,14 @@ #include "al/effect.h" #include "al/filter.h" #include "albit.h" -#include "alconfig.h" +#include "alnumeric.h" +#include "atomic.h" #include "backends/base.h" -#include "core/bformatdec.h" -#include "core/bs2b.h" -#include "core/front_stablizer.h" +#include "core/devformat.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mastering.h" -#include "core/uhjfilter.h" +#include "flexarray.h" namespace { @@ -27,13 +27,14 @@ using voidp = void*; } // namespace +namespace al { -ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type} +Device::Device(DeviceType type) : DeviceBase{type} { } -ALCdevice::~ALCdevice() +Device::~Device() { - TRACE("Freeing device %p\n", voidp{this}); + TRACE("Freeing device {}", voidp{this}); Backend = nullptr; @@ -41,35 +42,35 @@ ALCdevice::~ALCdevice() [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) - WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Buffer{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Effect{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) - WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s"); + WARN("{} Filter{} not deleted", count, (count==1)?"":"s"); } -void ALCdevice::enumerateHrtfs() +void Device::enumerateHrtfs() { mHrtfList = EnumerateHrtf(configValue({}, "hrtf-paths")); if(auto defhrtfopt = configValue({}, "default-hrtf")) { auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); if(iter == mHrtfList.end()) - WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str()); + WARN("Failed to find default HRTF \"{}\"", *defhrtfopt); else if(iter != mHrtfList.begin()) std::rotate(mHrtfList.begin(), iter, iter+1); } } -auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 +auto Device::getOutputMode1() const noexcept -> OutputMode1 { if(mContexts.load(std::memory_order_relaxed)->empty()) return OutputMode1::Any; @@ -88,9 +89,12 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: break; } return OutputMode1::Any; } + +} // namespace al diff --git a/libs/openal-soft/alc/device.h b/libs/openal-soft/alc/device.h index dd9335dc..1ca800c8 100644 --- a/libs/openal-soft/alc/device.h +++ b/libs/openal-soft/alc/device.h @@ -1,34 +1,29 @@ #ifndef ALC_DEVICE_H #define ALC_DEVICE_H -#include +#include "config.h" + #include #include #include #include -#include #include #include -#include +#include #include +#include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" #include "core/device.h" -#include "inprogext.h" #include "intrusive_ptr.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "al/eax/x_ram.h" #endif // ALSOFT_EAX -struct ALbuffer; -struct ALeffect; -struct ALfilter; struct BackendBase; struct BufferSubList; struct EffectSubList; @@ -37,7 +32,11 @@ struct FilterSubList; using uint = unsigned int; -struct ALCdevice : public al::intrusive_ref, DeviceBase { +struct ALCdevice { virtual ~ALCdevice() = default; }; + +namespace al { + +struct Device final : public ALCdevice, al::intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc) from * being from being changed in multiple threads, or being accessed while * being changed. It's also used to serialize calls to the backend. @@ -87,7 +86,7 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { std::mutex FilterLock; std::vector FilterList; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX ALuint eax_x_ram_free_size{eax_x_ram_max_size}; #endif // ALSOFT_EAX @@ -96,35 +95,41 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { std::unordered_map mEffectNames; std::unordered_map mFilterNames; - ALCdevice(DeviceType type); - ~ALCdevice(); + std::string mVendorOverride; + std::string mVersionOverride; + std::string mRendererOverride; + + explicit Device(DeviceType type); + ~Device() final; void enumerateHrtfs(); bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) - { return GetConfigValueBool(DeviceName, block, key, def); } + { return GetConfigValueBool(mDeviceName, block, key, def); } template - inline std::optional configValue(const std::string_view block, const std::string_view key) = delete; + auto configValue(const std::string_view block, const std::string_view key) -> std::optional = delete; }; -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueStr(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueInt(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueUInt(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueFloat(DeviceName, block, key); } -template<> -inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) -{ return ConfigValueBool(DeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueStr(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueInt(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueUInt(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueFloat(mDeviceName, block, key); } +template<> inline +auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional +{ return ConfigValueBool(mDeviceName, block, key); } + +} // namespace al /** Stores the latest ALC device error. */ -void alcSetError(ALCdevice *device, ALCenum errorCode); +void alcSetError(al::Device *device, ALCenum errorCode); #endif diff --git a/libs/openal-soft/alc/effects/autowah.cpp b/libs/openal-soft/alc/effects/autowah.cpp index 0b5aa1d2..90006c15 100644 --- a/libs/openal-soft/alc/effects/autowah.cpp +++ b/libs/openal-soft/alc/effects/autowah.cpp @@ -122,7 +122,7 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; @@ -214,7 +214,7 @@ void AutowahState::process(const size_t samplesToDo, chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut.data(), samplesToDo}, samplesOut[outidx].data(), + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx], chandata->mCurrentGain, chandata->mTargetGain, samplesToDo); ++chandata; } diff --git a/libs/openal-soft/alc/effects/base.h b/libs/openal-soft/alc/effects/base.h index 9bbbfc71..a64880d2 100644 --- a/libs/openal-soft/alc/effects/base.h +++ b/libs/openal-soft/alc/effects/base.h @@ -12,21 +12,18 @@ inline float ReverbBoost{1.0f}; EffectStateFactory *NullStateFactory_getFactory(); EffectStateFactory *ReverbStateFactory_getFactory(); -EffectStateFactory *StdReverbStateFactory_getFactory(); -EffectStateFactory *AutowahStateFactory_getFactory(); EffectStateFactory *ChorusStateFactory_getFactory(); +EffectStateFactory *AutowahStateFactory_getFactory(); EffectStateFactory *CompressorStateFactory_getFactory(); EffectStateFactory *DistortionStateFactory_getFactory(); EffectStateFactory *EchoStateFactory_getFactory(); EffectStateFactory *EqualizerStateFactory_getFactory(); -EffectStateFactory *FlangerStateFactory_getFactory(); EffectStateFactory *FshifterStateFactory_getFactory(); EffectStateFactory *ModulatorStateFactory_getFactory(); EffectStateFactory *PshifterStateFactory_getFactory(); EffectStateFactory* VmorpherStateFactory_getFactory(); -EffectStateFactory *DedicatedDialogStateFactory_getFactory(); -EffectStateFactory *DedicatedLfeStateFactory_getFactory(); +EffectStateFactory *DedicatedStateFactory_getFactory(); EffectStateFactory *ConvolutionStateFactory_getFactory(); diff --git a/libs/openal-soft/alc/effects/chorus.cpp b/libs/openal-soft/alc/effects/chorus.cpp index 98a4a408..414dd458 100644 --- a/libs/openal-soft/alc/effects/chorus.cpp +++ b/libs/openal-soft/alc/effects/chorus.cpp @@ -58,7 +58,7 @@ constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, i constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); -struct ChorusState : public EffectState { +struct ChorusState final : public EffectState { std::vector mDelayBuffer; uint mOffset{0}; @@ -94,35 +94,18 @@ struct ChorusState : public EffectState { const float delay, const float depth, const float feedback, const float rate, int phase, const EffectTarget target); - void deviceUpdate(const DeviceBase *device, const BufferStorage*) override - { deviceUpdate(device, ChorusMaxDelay); } + void deviceUpdate(const DeviceBase *device, const BufferStorage*) final; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, - const EffectTarget target) override - { - auto &props = std::get(*props_); - update(context, slot, props.Waveform, props.Delay, props.Depth, props.Feedback, props.Rate, - props.Phase, target); - } + const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) final; }; -struct FlangerState final : public ChorusState { - void deviceUpdate(const DeviceBase *device, const BufferStorage*) final - { ChorusState::deviceUpdate(device, FlangerMaxDelay); } - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, - const EffectTarget target) final - { - auto &props = std::get(*props_); - ChorusState::update(context, slot, props.Waveform, props.Delay, props.Depth, - props.Feedback, props.Rate, props.Phase, target); - } -}; - -void ChorusState::deviceUpdate(const DeviceBase *Device, const float MaxDelay) +void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - const auto frequency = static_cast(Device->Frequency); + constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); + const auto frequency = static_cast(Device->mSampleRate); const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); @@ -136,34 +119,40 @@ void ChorusState::deviceUpdate(const DeviceBase *Device, const float MaxDelay) } void ChorusState::update(const ContextBase *context, const EffectSlot *slot, - const ChorusWaveform waveform, const float delay, const float depth, const float feedback, - const float rate, int phase, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits}; + auto &props = std::get(*props_); /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); - mWaveform = waveform; + mWaveform = props.Waveform; - mDelay = std::max(float2int(std::round(delay*frequency*gCubicTable.sTableSteps)), mindelay); - mDepth = std::min(static_cast(mDelay)*depth, static_cast(mDelay-mindelay)); + 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 = feedback; + mFeedback = props.Feedback; /* Gains for left and right sides */ const bool ispairwise{device->mRenderMode == RenderMode::Pairwise}; const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw}; const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw}; + /* Attenuate the outputs by -3dB, since we duplicate a single mono input to + * separate left/right outputs. + */ + const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target); - if(!(rate > 0.0f)) + if(!(props.Rate > 0.0f)) { mLfoOffset = 0; mLfoRange = 1; @@ -176,7 +165,8 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot, * max range to avoid overflow when calculating the displacement. */ static constexpr int range_limit{std::numeric_limits::max()/360 - 180}; - const uint lfo_range{float2uint(std::min(std::round(frequency/rate), float{range_limit}))}; + const auto range = std::round(frequency / props.Rate); + const uint lfo_range{float2uint(std::min(range, float{range_limit}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; @@ -191,7 +181,8 @@ void ChorusState::update(const ContextBase *context, const EffectSlot *slot, } /* Calculate lfo phase displacement */ - if(phase < 0) phase = 360 + phase; + auto phase = props.Phase; + if(phase < 0) phase += 360; mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } @@ -204,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -214,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -245,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -255,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -322,10 +305,10 @@ void ChorusState::process(const size_t samplesToDo, const al::span{new ChorusState{}}; } }; - -/* Flanger is basically a chorus with a really short delay. They can both use - * the same processing functions, so piggyback flanger on the chorus functions. - */ -struct FlangerStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new FlangerState{}}; } -}; - } // namespace EffectStateFactory *ChorusStateFactory_getFactory() @@ -352,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory() static ChorusStateFactory ChorusFactory{}; return &ChorusFactory; } - -EffectStateFactory *FlangerStateFactory_getFactory() -{ - static FlangerStateFactory FlangerFactory{}; - return &FlangerFactory; -} diff --git a/libs/openal-soft/alc/effects/compressor.cpp b/libs/openal-soft/alc/effects/compressor.cpp index ed5c9be6..27245b69 100644 --- a/libs/openal-soft/alc/effects/compressor.cpp +++ b/libs/openal-soft/alc/effects/compressor.cpp @@ -74,6 +74,7 @@ struct CompressorState final : public EffectState { 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; @@ -88,8 +89,8 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const float attackCount{static_cast(device->Frequency) * AttackTime}; - const float releaseCount{static_cast(device->Frequency) * ReleaseTime}; + const float attackCount{static_cast(device->mSampleRate) * AttackTime}; + const float releaseCount{static_cast(device->mSampleRate) * ReleaseTime}; /* Calculate per-sample multipliers to attack and release at the desired * rates. @@ -115,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot, void CompressorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - for(size_t base{0u};base < samplesToDo;) + /* Generate the per-sample gains from the signal envelope. */ + float env{mEnvFollower}; + if(mEnabled) { - std::array gains; - const size_t td{std::min(gains.size(), samplesToDo-base)}; - - /* Generate the per-sample gains from the signal envelope. */ - float env{mEnvFollower}; - if(mEnabled) + for(size_t i{0u};i < samplesToDo;++i) { - for(size_t i{0u};i < td;++i) - { - /* Clamp the absolute amplitude to the defined envelope limits, - * then attack or release the envelope to reach it. - */ - const float amplitude{std::clamp(std::fabs(samplesIn[0][base+i]), AmpEnvelopeMin, - AmpEnvelopeMax)}; - if(amplitude > env) - env = std::min(env*mAttackMult, amplitude); - else if(amplitude < env) - env = std::max(env*mReleaseMult, amplitude); - - /* Apply the reciprocal of the envelope to normalize the volume - * (compress the dynamic range). - */ - gains[i] = 1.0f / env; - } - } - else - { - /* Same as above, except the amplitude is forced to 1. This helps - * ensure smooth gain changes when the compressor is turned on and - * off. + /* Clamp the absolute amplitude to the defined envelope limits, + * then attack or release the envelope to reach it. */ - for(size_t i{0u};i < td;++i) - { - const float amplitude{1.0f}; - if(amplitude > env) - env = std::min(env*mAttackMult, amplitude); - else if(amplitude < env) - env = std::max(env*mReleaseMult, amplitude); + const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin, + AmpEnvelopeMax)}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - gains[i] = 1.0f / env; - } + /* Apply the reciprocal of the envelope to normalize the volume + * (compress the dynamic range). + */ + mGains[i] = 1.0f / env; } - mEnvFollower = env; - - /* Now compress the signal amplitude to output. */ - auto chan = mChans.cbegin(); - for(const auto &input : samplesIn) + } + else + { + /* Same as above, except the amplitude is forced to 1. This helps + * ensure smooth gain changes when the compressor is turned on and off. + */ + for(size_t i{0u};i < samplesToDo;++i) { - const size_t outidx{chan->mTarget}; - if(outidx != InvalidChannelIndex) - { - const auto src = al::span{input}.subspan(base); - float *RESTRICT dst{samplesOut[outidx].data() + base}; - const float gain{chan->mGain}; - if(!(std::fabs(gain) > GainSilenceThreshold)) - { - for(size_t i{0u};i < td;i++) - dst[i] += src[i] * gains[i] * gain; - } - } - ++chan; - } + const float amplitude{1.0f}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - base += td; + mGains[i] = 1.0f / env; + } + } + mEnvFollower = env; + + /* Now compress the signal amplitude to output. */ + auto chan = mChans.cbegin(); + for(const auto &input : samplesIn) + { + const size_t outidx{chan->mTarget}; + if(outidx != InvalidChannelIndex) + { + const auto dst = al::span{samplesOut[outidx]}; + const float gain{chan->mGain}; + if(!(std::fabs(gain) > GainSilenceThreshold)) + { + for(size_t i{0u};i < samplesToDo;++i) + dst[i] += input[i] * mGains[i] * gain; + } + } + ++chan; } } diff --git a/libs/openal-soft/alc/effects/convolution.cpp b/libs/openal-soft/alc/effects/convolution.cpp index 8280dcb8..cafa4e6f 100644 --- a/libs/openal-soft/alc/effects/convolution.cpp +++ b/libs/openal-soft/alc/effects/convolution.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include #include @@ -11,11 +12,10 @@ #include #include #include -#include -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_NEON) +#elif HAVE_NEON #include #endif @@ -171,7 +171,7 @@ constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; void apply_fir(al::span dst, const al::span input, const al::span filter) { auto src = input.begin(); -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS std::generate(dst.begin(), dst.end(), [&src,filter] { __m128 r4{_mm_setzero_ps()}; @@ -189,7 +189,7 @@ void apply_fir(al::span dst, const al::span input, const al: return _mm_cvtss_f32(r4); }); -#elif defined(HAVE_NEON) +#elif HAVE_NEON std::generate(dst.begin(), dst.end(), [&src,filter] { @@ -227,7 +227,7 @@ struct ConvolutionState final : public EffectState { al::vector,16> mFilter; al::vector,16> mOutput; - PFFFTSetup mFft{}; + PFFFTSetup mFft; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mFftWorkBuffer{}; @@ -237,7 +237,7 @@ struct ConvolutionState final : public EffectState { struct ChannelData { alignas(16) FloatBufferLine mBuffer{}; float mHfScale{}, mLfScale{}; - BandSplitter mFilter{}; + BandSplitter mFilter; std::array Current{}; std::array Target{}; }; @@ -264,8 +264,8 @@ void ConvolutionState::NormalMix(const al::span samplesOut, const size_t samplesToDo) { for(auto &chan : mChans) - MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current.data(), - chan.Target.data(), samplesToDo, 0); + MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current, + chan.Target, samplesToDo, 0); } void ConvolutionState::UpsampleMix(const al::span samplesOut, @@ -273,9 +273,9 @@ void ConvolutionState::UpsampleMix(const al::span samplesOut, { for(auto &chan : mChans) { - const al::span src{chan.mBuffer.data(), samplesToDo}; + const auto src = al::span{chan.mBuffer}.first(samplesToDo); chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); - MixSamples(src, samplesOut, chan.Current.data(), chan.Target.data(), samplesToDo, 0); + MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } } @@ -322,13 +322,13 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag * called very infrequently, go ahead and use the polyphase resampler. */ PPhaseResampler resampler; - if(device->Frequency != buffer->mSampleRate) - resampler.init(buffer->mSampleRate, device->Frequency); + if(device->mSampleRate != buffer->mSampleRate) + resampler.init(buffer->mSampleRate, device->mSampleRate); const auto resampledCount = static_cast( - (uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) / + (uint64_t{buffer->mSampleLen}*device->mSampleRate+(buffer->mSampleRate-1)) / buffer->mSampleRate); - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &e : mChans) e.mFilter = splitter; diff --git a/libs/openal-soft/alc/effects/dedicated.cpp b/libs/openal-soft/alc/effects/dedicated.cpp index 371f02b9..df1bd5ed 100644 --- a/libs/openal-soft/alc/effects/dedicated.cpp +++ b/libs/openal-soft/alc/effects/dedicated.cpp @@ -43,7 +43,7 @@ namespace { using uint = unsigned int; -struct DedicatedState : public EffectState { +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. @@ -53,90 +53,72 @@ struct DedicatedState : public EffectState { void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) final; }; -struct DedicatedLfeState final : public DedicatedState { - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) final; -}; - void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f); } void DedicatedState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); - const float Gain{slot->Gain * std::get(*props).Gain}; + auto &props = std::get(*props_); + const float Gain{slot->Gain * props.Gain}; - /* 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) + if(props.Target == DedicatedProps::Dialog) { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; + /* Dialog goes to the front-center speaker if it exists, otherwise it + * plays from the front-center location. + */ + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) + { + mOutTarget = target.RealOut->Buffer; + mTargetGains[idx] = Gain; + } + else + { + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); + + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + } } - else + else if(props.Target == DedicatedProps::Lfe) { - static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); - - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs, Gain, mTargetGains); - } -} - -void DedicatedLfeState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) -{ - std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); - - const float Gain{slot->Gain * std::get(*props).Gain}; - - const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; - if(idx != InvalidChannelIndex) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) + { + mOutTarget = target.RealOut->Buffer; + mTargetGains[idx] = Gain; + } } } void DedicatedState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains.data(), - mTargetGains.data(), samplesToDo, 0); + MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, + samplesToDo, 0); } -struct DedicatedDialogStateFactory final : public EffectStateFactory { +struct DedicatedStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new DedicatedState{}}; } }; -struct DedicatedLfeStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new DedicatedLfeState{}}; } -}; - } // namespace -EffectStateFactory *DedicatedDialogStateFactory_getFactory() +EffectStateFactory *DedicatedStateFactory_getFactory() { - static DedicatedDialogStateFactory DedicatedFactory{}; - return &DedicatedFactory; -} - -EffectStateFactory *DedicatedLfeStateFactory_getFactory() -{ - static DedicatedLfeStateFactory DedicatedFactory{}; + static DedicatedStateFactory DedicatedFactory{}; return &DedicatedFactory; } diff --git a/libs/openal-soft/alc/effects/distortion.cpp b/libs/openal-soft/alc/effects/distortion.cpp index 6d5ba670..963ddad1 100644 --- a/libs/openal-soft/alc/effects/distortion.cpp +++ b/libs/openal-soft/alc/effects/distortion.cpp @@ -87,7 +87,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot, /* Divide normalized frequency by the amount of oversampling done during * processing. */ - auto frequency = static_cast(device->Frequency); + auto frequency = static_cast(device->mSampleRate); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); cutoff = props.EQCenter; @@ -124,7 +124,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span>= 2; auto outgains = mGain.cbegin(); - for(FloatBufferLine &RESTRICT output : samplesOut) + auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ const float gain{*(outgains++)}; if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; + return; - for(size_t i{0u};i < todo;i++) - output[base+i] += gain * mBuffer[1][i*4]; - } + auto src = mBuffer[1].cbegin(); + const auto dst = al::span{output}.subspan(base, todo); + auto dec_sample = [gain,&src](float sample) noexcept -> float + { + sample += *src * gain; + src += 4; + return sample; + }; + std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample); + }; + std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline); base += todo; } diff --git a/libs/openal-soft/alc/effects/echo.cpp b/libs/openal-soft/alc/effects/echo.cpp index c029eb5b..6809ddcb 100644 --- a/libs/openal-soft/alc/effects/echo.cpp +++ b/libs/openal-soft/alc/effects/echo.cpp @@ -78,7 +78,7 @@ struct EchoState final : public EffectState { void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - const auto frequency = static_cast(Device->Frequency); + 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 @@ -100,7 +100,7 @@ void EchoState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; @@ -160,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span(*props_); const DeviceBase *device{context->mDevice}; - auto frequency = static_cast(device->Frequency); + auto frequency = static_cast(device->mSampleRate); /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, @@ -169,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, void EqualizerState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const al::span buffer{mSampleBuffer.data(), samplesToDo}; + const auto buffer = al::span{mSampleBuffer}.first(samplesToDo); auto chan = mChans.begin(); for(const auto &input : samplesIn) { - const size_t outidx{chan->mTargetChannel}; - if(outidx != InvalidChannelIndex) + if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex) { - const al::span inbuf{input.data(), samplesToDo}; + const auto inbuf = al::span{input}.first(samplesToDo); DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer); DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer); - MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain, samplesToDo); } ++chan; diff --git a/libs/openal-soft/alc/effects/fshifter.cpp b/libs/openal-soft/alc/effects/fshifter.cpp index e4d473ba..ee04fd71 100644 --- a/libs/openal-soft/alc/effects/fshifter.cpp +++ b/libs/openal-soft/alc/effects/fshifter.cpp @@ -134,7 +134,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props.Frequency / static_cast(device->Frequency)}; + const float step{props.Frequency / static_cast(device->mSampleRate)}; mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); switch(props.LeftDirection) @@ -239,8 +239,8 @@ void FshifterState::process(const size_t samplesToDo, const al::span 0.0f - ? static_cast(device->Frequency)/props.Frequency + 0.5f + ? static_cast(device->mSampleRate)/props.Frequency + 0.5f : 1.0f}; const uint range{static_cast(std::clamp(samplesPerCycle, 1.0f, - static_cast(device->Frequency)))}; + static_cast(device->mSampleRate)))}; mIndex = static_cast(uint64_t{mIndex} * range / mRange); mRange = range; @@ -155,7 +155,7 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, mSampleGen.emplace(); } - float f0norm{props.HighPassCutoff / static_cast(device->Frequency)}; + float f0norm{props.HighPassCutoff / static_cast(device->mSampleRate)}; f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); @@ -198,14 +198,13 @@ void ModulatorState::process(const size_t samplesToDo, const al::spanmTargetChannel}; - if(outidx != InvalidChannelIndex) + if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex) { - chandata->mFilter.process({input.data(), samplesToDo}, mBuffer); + chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer); std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(), mBuffer.begin(), std::multiplies<>{}); - MixSamples({mBuffer.data(), samplesToDo}, samplesOut[outidx].data(), + MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx], chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz)); } ++chandata; diff --git a/libs/openal-soft/alc/effects/pshifter.cpp b/libs/openal-soft/alc/effects/pshifter.cpp index 08c8564c..8b3c6d29 100644 --- a/libs/openal-soft/alc/effects/pshifter.cpp +++ b/libs/openal-soft/alc/effects/pshifter.cpp @@ -306,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo, } /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains.data(), - mTargetGains.data(), std::max(samplesToDo, 512_uz), 0); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, + std::max(samplesToDo, 512_uz), 0); } diff --git a/libs/openal-soft/alc/effects/reverb.cpp b/libs/openal-soft/alc/effects/reverb.cpp index 392971b2..7e907911 100644 --- a/libs/openal-soft/alc/effects/reverb.cpp +++ b/libs/openal-soft/alc/effects/reverb.cpp @@ -26,12 +26,10 @@ #include #include #include +#include #include -#include -#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" @@ -258,17 +256,17 @@ 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. */ - size_t Mask{0u}; - float *Line{}; + al::span mLine; /* Given the allocated sample buffer, this function updates each delay line * offset. */ - void realizeLineOffset(float *sampleBuffer) noexcept - { Line = sampleBuffer; } + void realizeLineOffset(al::span sampleBuffer) noexcept + { mLine = sampleBuffer; } /* Calculate the length of a delay line and store its mask and offset. */ - size_t calcLineLength(const float length, const float frequency, const uint extra) + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. @@ -276,50 +274,47 @@ struct DelayLineI { uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); - /* All lines share a single sample buffer. */ - Mask = samples - 1; - /* Return the sample count for accumulation. */ return samples*NUM_LINES; } - - [[nodiscard]] - auto &get(size_t i, size_t chan) const noexcept { return Line[i*NUM_LINES + chan]; } - void set(size_t i, const std::array in) const noexcept - { std::copy_n(in.begin(), NUM_LINES, Line + i*NUM_LINES); } }; struct DelayLineU { - size_t Mask{0u}; - float *Line{}; + al::span mLine; - void realizeLineOffset(float *sampleBuffer) noexcept - { Line = sampleBuffer; } + void realizeLineOffset(al::span sampleBuffer) noexcept + { + assert(sampleBuffer.size() > 4 && !(sampleBuffer.size() & (sampleBuffer.size()-1))); + mLine = sampleBuffer; + } - size_t calcLineLength(const float length, const float frequency, const uint extra) + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); - Mask = samples - 1; - return samples*NUM_LINES; } [[nodiscard]] - auto get(size_t chan) const noexcept { return al::span{Line + chan*(Mask+1), Mask+1}; } - - void write(size_t offset, const size_t c, const float *in, const size_t count) const noexcept + auto get(size_t chan) const noexcept { - ASSUME(count > 0); - float *RESTRICT out{al::assume_aligned<16>(Line + c*(Mask+1))}; - for(size_t i{0u};i < count;) + const size_t stride{mLine.size() / NUM_LINES}; + return mLine.subspan(chan*stride, stride); + } + + void write(size_t offset, const size_t c, al::span in) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; + const auto output = mLine.subspan(c*stride); + while(!in.empty()) { - offset &= Mask; - const size_t td{std::min(Mask+1 - offset, count - i)}; - std::copy_n(in+i, td, out+offset); + offset &= stride-1; + const size_t td{std::min(stride - offset, in.size())}; + std::copy_n(in.begin(), td, output.begin() + ptrdiff_t(offset)); offset += td; - i += td; + in = in.subspan(td); } } @@ -338,12 +333,11 @@ struct DelayLineU { void writeReflected(size_t offset, const al::span in, const size_t count) const noexcept { - const size_t stride{Mask+1}; - ASSUME(count > 0); + const size_t stride{mLine.size() / NUM_LINES}; for(size_t i{0u};i < count;) { - offset &= Mask; - size_t td{std::min(Mask+1 - offset, count - i)}; + offset &= stride-1; + size_t td{std::min(stride - offset, count - i)}; do { const std::array src{in[0][i], in[1][i], in[2][i], in[3][i]}; ++i; @@ -354,10 +348,10 @@ struct DelayLineU { (src[2] - src[0] - src[1] - src[3]) * 0.5f, (src[3] - src[0] - src[1] - src[2] ) * 0.5f }; - Line[0*stride + offset] = f[0]; - Line[1*stride + offset] = f[1]; - Line[2*stride + offset] = f[2]; - Line[3*stride + offset] = f[3]; + 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); } @@ -370,7 +364,7 @@ struct VecAllpass { std::array Offset{}; void process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo) noexcept; + const float xCoeff, const float yCoeff, const size_t todo) const noexcept; }; struct Allpass4 { @@ -379,7 +373,7 @@ struct Allpass4 { std::array Offset{}; void process(const al::span samples, const size_t offset, - const size_t todo) noexcept; + const size_t todo) const noexcept; }; struct T60Filter { @@ -407,17 +401,24 @@ struct EarlyReflections { */ DelayLineU Delay; std::array Offset{}; - std::array Coeff{}; + float Coeff{}; /* The gain for each output channel based on 3D panning. */ struct OutGains { std::array Current{}; std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } }; std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); + + void clear() + { + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); + } }; @@ -425,16 +426,23 @@ struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ - uint Index{}, Step{}; + uint Index{0u}, Step{1u}; /* The depth of frequency change, in samples. */ - float Depth{}; + float Depth{0.0f}; std::array ModDelays{}; void updateModulator(float modTime, float modDepth, float frequency); - void calcDelays(size_t todo); + auto calcDelays(size_t todo) -> al::span; + + void clear() noexcept + { + Index = 0u; + Step = 1u; + Depth = 0.0f; + } }; struct LateReverb { @@ -459,6 +467,8 @@ struct LateReverb { struct OutGains { std::array Current{}; std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } }; std::array Gains{}; @@ -466,10 +476,11 @@ struct LateReverb { const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm, const float frequency); - void clear() noexcept + void clear() { - for(auto &filter : T60) - filter.clear(); + std::for_each(T60.begin(), T60.end(), std::mem_fn(&T60Filter::clear)); + Mod.clear(); + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); } }; @@ -478,6 +489,7 @@ struct ReverbPipeline { struct FilterPair { BiquadFilter Lp; BiquadFilter Hp; + void clear() noexcept { Lp.clear(); Hp.clear(); } }; std::array mFilter; @@ -488,13 +500,13 @@ struct ReverbPipeline { /* Tap points for early reflection input delay. */ std::array,NUM_LINES> mEarlyDelayTap{}; - std::array,NUM_LINES> mEarlyDelayCoeff{}; + std::array mEarlyDelayCoeff{}; /* Tap points for late reverb feed and delay. */ std::array,NUM_LINES> mLateDelayTap{}; /* Coefficients for the all-pass and line scattering matrices. */ - float mMixX{0.0f}; + float mMixX{1.0f}; float mMixY{0.0f}; EarlyReflections mEarly; @@ -506,7 +518,7 @@ struct ReverbPipeline { size_t mFadeSampleCount{1}; void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, - const float density_mult, const float decayTime, const float frequency); + const float density_mult, const float frequency); void update3DPanning(const al::span ReflectionsPan, const al::span LateReverbPan, const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); @@ -520,17 +532,15 @@ struct ReverbPipeline { void clear() noexcept { - for(auto &filter : mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } + std::for_each(mFilter.begin(), mFilter.end(), std::mem_fn(&FilterPair::clear)); + mEarlyDelayTap = {}; + mEarlyDelayCoeff = {}; + mLateDelayTap = {}; + mEarly.clear(); mLate.clear(); - for(auto &filters : mAmbiSplitter) - { - for(auto &filter : filters) - filter.clear(); - } + auto clear_filters = [](const al::span filters) + { std::for_each(filters.begin(), filters.end(), std::mem_fn(&BandSplitter::clear)); }; + std::for_each(mAmbiSplitter.begin(), mAmbiSplitter.end(), clear_filters); } }; @@ -589,32 +599,26 @@ struct ReverbState final : public EffectState { void MixOutPlain(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) const { - ASSUME(todo > 0); - /* When not upsampling, the panning gains convert to B-Format and pan * at the same time. */ auto inBuffer = mEarlySamples.cbegin(); for(auto &gains : pipeline.mEarly.Gains) { - MixSamples(al::span{inBuffer->cbegin(), todo}, samplesOut, gains.Current.data(), - gains.Target.data(), todo, 0); - ++inBuffer; + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } inBuffer = mLateSamples.cbegin(); for(auto &gains : pipeline.mLate.Gains) { - MixSamples(al::span{inBuffer->cbegin(), todo}, samplesOut, gains.Current.data(), - gains.Target.data(), todo, 0); - ++inBuffer; + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } } void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { - ASSUME(todo > 0); - auto DoMixRow = [](const al::span OutBuffer, const al::span Gains, const al::span InSamples) { @@ -622,16 +626,14 @@ struct ReverbState final : public EffectState { std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); for(const float gain : Gains) { - const float *RESTRICT input{al::assume_aligned<16>(inBuffer->data())}; + if(std::fabs(gain) > GainSilenceThreshold) + { + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(OutBuffer.begin(), OutBuffer.end(), inBuffer->cbegin(), + OutBuffer.begin(), mix_sample); + } ++inBuffer; - - if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), - mix_sample); } }; @@ -639,8 +641,8 @@ struct ReverbState final : public EffectState { * 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 al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; - float hfscale{mOrderScales[0]}; + const auto tmpspan = al::span{mTempLine}.first(todo); + auto hfscale = float{mOrderScales[0]}; auto splitter = pipeline.mAmbiSplitter[0].begin(); auto a2bcoeffs = EarlyA2B.cbegin(); for(auto &gains : pipeline.mEarly.Gains) @@ -650,10 +652,10 @@ struct ReverbState final : public EffectState { /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. */ - splitter->processHfScale(tmpspan, hfscale); - ++splitter; hfscale = mOrderScales[1]; + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, gains.Current.data(), gains.Target.data(), todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } hfscale = mOrderScales[0]; splitter = pipeline.mAmbiSplitter[1].begin(); @@ -662,10 +664,10 @@ struct ReverbState final : public EffectState { { DoMixRow(tmpspan, *(a2bcoeffs++), mLateSamples); - splitter->processHfScale(tmpspan, hfscale); - ++splitter; hfscale = mOrderScales[1]; + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, gains.Current.data(), gains.Target.data(), todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } } @@ -709,7 +711,7 @@ void ReverbState::allocLines(const float frequency) */ static constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; - std::array lineoffsets{}; + std::array linelengths{}; size_t oidx{0}; size_t totalSamples{0u}; @@ -719,7 +721,7 @@ void ReverbState::allocLines(const float frequency) */ float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; size_t count{mMainDelay.calcLineLength(length, frequency, BufferLineSize)}; - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; for(auto &pipeline : mPipelines) { @@ -727,25 +729,25 @@ void ReverbState::allocLines(const float frequency) float{NUM_LINES}}; length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; /* The early vector all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; count = pipeline.mEarly.VecAp.Delay.calcLineLength(length, frequency, 0); - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; /* The early reflection line. */ length = EARLY_LINE_LENGTHS.back() * multiplier; count = pipeline.mEarly.Delay.calcLineLength(length, frequency, MAX_UPDATE_SAMPLES); - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; /* The late vector all-pass line. */ length = LATE_ALLPASS_LENGTHS.back() * multiplier; count = pipeline.mLate.VecAp.Delay.calcLineLength(length, frequency, 0); - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; /* The late delay lines are calculated from the largest maximum density @@ -754,74 +756,46 @@ void ReverbState::allocLines(const float frequency) */ length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; count = pipeline.mLate.Delay.calcLineLength(length, frequency, 4); - lineoffsets[oidx++] = totalSamples; + linelengths[oidx++] = count; totalSamples += count; } - assert(oidx == lineoffsets.size()); + assert(oidx == linelengths.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); + std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); /* Update all delays to reflect the new sample buffer. */ + auto bufferspan = al::span{mSampleBuffer}; oidx = 0; - mMainDelay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + mMainDelay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); for(auto &pipeline : mPipelines) { - pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); - pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); - pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); - pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); - pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + pipeline.mLateDelayIn.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); } + assert(oidx == linelengths.size()); } void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { - const auto frequency = static_cast(device->Frequency); + const auto frequency = static_cast(device->mSampleRate); /* Allocate the delay lines. */ allocLines(frequency); - for(auto &pipeline : mPipelines) - { - /* Clear filters and gain coefficients since the delay lines were all - * just cleared (if not reallocated). - */ - for(auto &filter : pipeline.mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } - - for(auto &coeffs : pipeline.mEarlyDelayCoeff) - coeffs.fill(0.0f); - - pipeline.mLate.DensityGain = 0.0f; - for(auto &t60 : pipeline.mLate.T60) - { - t60.MidGain = 0.0f; - t60.HFFilter.clear(); - t60.LFFilter.clear(); - } - - pipeline.mLate.Mod.Index = 0; - pipeline.mLate.Mod.Step = 1; - pipeline.mLate.Mod.Depth = 0.0f; - - for(auto &gains : pipeline.mEarly.Gains) - { - gains.Current.fill(0.0f); - gains.Target.fill(0.0f); - } - for(auto &gains : pipeline.mLate.Gains) - { - gains.Current.fill(0.0f); - gains.Target.fill(0.0f); - } - } + std::for_each(mPipelines.begin(), mPipelines.end(), std::mem_fn(&ReverbPipeline::clear)); mPipelineState = DeviceClear; /* Reset offset base. */ @@ -837,14 +811,14 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) mUpmixOutput = false; mOrderScales.fill(1.0f); } - mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); - for(auto &pipeline : mPipelines) + + auto splitter = BandSplitter{device->mXOverFreq / frequency}; + auto set_splitters = [&splitter](ReverbPipeline &pipeline) { - std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), - pipeline.mAmbiSplitter[0][0]); - std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), - pipeline.mAmbiSplitter[0][0]); - } + std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), splitter); + std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), splitter); + }; + std::for_each(mPipelines.begin(), mPipelines.end(), set_splitters); } /************************************** @@ -952,10 +926,15 @@ void EarlyReflections::updateLines(const float density_mult, const float diffusi /* Calculate the delay length of each delay line. */ length = EARLY_LINE_LENGTHS[i] * density_mult; Offset[i] = float2uint(length * frequency); - - /* Calculate the gain (coefficient) for each line. */ - Coeff[i] = CalcDecayCoeff(length, decayTime); } + + /* Calculate the gain (coefficient) for the secondary reflections based on + * the average delay and decay time. + */ + const auto length = std::reduce(EARLY_LINE_LENGTHS.begin(), EARLY_LINE_LENGTHS.end(), 0.0f) + / float{EARLY_LINE_LENGTHS.size()} * density_mult; + Coeff = CalcDecayCoeff(length, decayTime); + } /* Update the EAX modulation step and depth. Keep in mind that this kind of @@ -1062,7 +1041,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Update the offsets for the main effect delay line. */ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, - const float lateDelay, const float density_mult, const float decayTime, const float frequency) + const float lateDelay, const float density_mult, const float frequency) { /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. @@ -1074,11 +1053,11 @@ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, * delay path and offsets that would continue the propagation naturally * into the late lines. */ + mEarlyDelayCoeff[1] = gain; for(size_t i{0u};i < NUM_LINES;i++) { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); - mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime) * gain; /* Reduce the late delay tap by the shortest early delay line length to * compensate for the late line input being fed by the delayed early @@ -1104,14 +1083,14 @@ std::array,4> GetTransformFromVector(const al::span norm; + std::array norm{{vec[0], vec[1], vec[2]}}; float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { const float scale{al::numbers::sqrt3_v / mag}; - norm[0] = vec[0] * -scale; - norm[1] = vec[1] * scale; - norm[2] = vec[2] * scale; + norm[0] *= -scale; + norm[1] *= scale; + norm[2] *= scale; mag = 1.0f; } else @@ -1120,9 +1099,9 @@ std::array,4> GetTransformFromVector(const al::span; - norm[1] = vec[1] * al::numbers::sqrt3_v; - norm[2] = vec[2] * al::numbers::sqrt3_v; + norm[0] *= -al::numbers::sqrt3_v; + norm[1] *= al::numbers::sqrt3_v; + norm[2] *= al::numbers::sqrt3_v; } return std::array,4>{{ @@ -1144,7 +1123,8 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa const auto earlymat = GetTransformFromVector(ReflectionsPan); const auto latemat = GetTransformFromVector(LateReverbPan); - const auto [earlycoeffs, latecoeffs] = [&]{ + const auto get_coeffs = [&] + { if(doUpmix) { /* When upsampling, combine the early and late transforms with the @@ -1172,7 +1152,7 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa return res; }; - return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); + return std::array{mult_matrix(earlymat), mult_matrix(latemat)}; } /* When not upsampling, combine the early and late A-to-B-Format @@ -1199,8 +1179,9 @@ void ReverbPipeline::update3DPanning(const al::span ReflectionsPa return res; }; - return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); - }(); + return std::array{mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)}; + }; + const auto [earlycoeffs, latecoeffs] = get_coeffs(); auto earlygains = mEarly.Gains.begin(); for(auto &coeffs : earlycoeffs) @@ -1215,7 +1196,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, { auto &props = std::get(*props_); const DeviceBase *Device{Context->mDevice}; - const auto frequency = static_cast(Device->Frequency); + const auto frequency = static_cast(Device->mSampleRate); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. @@ -1267,8 +1248,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, mCurrentPipeline = !mCurrentPipeline; auto &oldpipeline = mPipelines[!mCurrentPipeline]; - for(size_t j{0};j < NUM_LINES;++j) - oldpipeline.mEarlyDelayCoeff[j][1] = 0.0f; + oldpipeline.mEarlyDelayCoeff[1] = 0.0f; } auto &pipeline = mPipelines[mCurrentPipeline]; @@ -1277,7 +1257,7 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, /* Update the main effect delay and associated taps. */ pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, - density_mult, props.DecayTime, frequency); + density_mult, frequency); /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; @@ -1379,8 +1359,8 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) * whose combination of signs are being iterated. */ -inline auto VectorPartialScatter(const std::array &RESTRICT in, - const float xCoeff, const float yCoeff) noexcept +inline auto VectorPartialScatter(const std::array &in, const float xCoeff, + const float yCoeff) noexcept -> std::array { return std::array{ xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), @@ -1417,41 +1397,45 @@ void VectorScatterRev(const float xCoeff, const float yCoeff, * element with a scattering matrix (like the one above) and a diagonal * matrix of delay elements. */ -void VecAllpass::process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo) noexcept +void VecAllpass::process(const al::span samples, size_t main_offset, + const float xCoeff, const float yCoeff, const size_t todo) const noexcept { - const DelayLineI delay{Delay}; + const auto linelen = size_t{Delay.mLine.size()/NUM_LINES}; const float feedCoeff{Coeff}; ASSUME(todo > 0); - std::array vap_offset; - for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] = offset - Offset[j]; for(size_t i{0u};i < todo;) { - for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] &= delay.Mask; - offset &= delay.Mask; + std::array vap_offset{}; + std::transform(Offset.cbegin(), Offset.cend(), vap_offset.begin(), + [main_offset,mask=linelen-1](const size_t delay) noexcept -> size_t + { return (main_offset-delay) & mask; }); + main_offset &= linelen-1; - size_t maxoff{offset}; - for(size_t j{0u};j < NUM_LINES;j++) - maxoff = std::max(maxoff, vap_offset[j]); - size_t td{std::min(delay.Mask+1 - maxoff, todo - i)}; + const auto maxoff = std::accumulate(vap_offset.cbegin(), vap_offset.cend(), main_offset, + [](const size_t offset, const size_t apoffset) { return std::max(offset, apoffset); }); + size_t td{std::min(linelen - maxoff, todo - i)}; + + auto delayIn = Delay.mLine.begin(); + auto delayOut = Delay.mLine.begin() + ptrdiff_t(main_offset*NUM_LINES); + main_offset += td; do { - std::array f; + std::array f{}; for(size_t j{0u};j < NUM_LINES;j++) { const float input{samples[j][i]}; - const float out{delay.get(vap_offset[j]++, j) - feedCoeff*input}; + const float out{delayIn[vap_offset[j]*NUM_LINES + j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } + delayIn += NUM_LINES; ++i; - delay.set(offset++, VectorPartialScatter(f, xCoeff, yCoeff)); + f = VectorPartialScatter(f, xCoeff, yCoeff); + delayOut = std::copy_n(f.cbegin(), f.size(), delayOut); } while(--td); } } @@ -1460,7 +1444,7 @@ void VecAllpass::process(const al::span samples, siz * matrix. */ void Allpass4::process(const al::span samples, const size_t offset, - const size_t todo) noexcept + const size_t todo) const noexcept { const DelayLineU delay{Delay}; const float feedCoeff{Coeff}; @@ -1469,24 +1453,26 @@ void Allpass4::process(const al::span samples, const for(size_t j{0u};j < NUM_LINES;j++) { - float *buffer{delay.get(j).data()}; + auto smpiter = samples[j].begin(); + const auto buffer = delay.get(j); size_t dstoffset{offset}; size_t vap_offset{offset - Offset[j]}; for(size_t i{0u};i < todo;) { - vap_offset &= delay.Mask; - dstoffset &= delay.Mask; + vap_offset &= buffer.size()-1; + dstoffset &= buffer.size()-1; const size_t maxoff{std::max(dstoffset, vap_offset)}; - size_t td{std::min(delay.Mask+1 - maxoff, todo - i)}; + const size_t td{std::min(buffer.size() - maxoff, todo - i)}; - do { - const float x{samples[j][i]}; + auto proc_sample = [buffer,feedCoeff,&vap_offset,&dstoffset](const float x) -> float + { const float y{buffer[vap_offset++] - feedCoeff*x}; buffer[dstoffset++] = x + feedCoeff*y; - - samples[j][i++] = y; - } while(--td); + return y; + }; + smpiter = std::transform(smpiter, smpiter+td, smpiter, proc_sample); + i += td; } } } @@ -1516,7 +1502,7 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { @@ -1525,95 +1511,114 @@ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, /* First, load decorrelated samples from the main delay line as the * primary reflections. */ - const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;j++) + const auto fadeStep = 1.0f / static_cast(todo); + const auto earlycoeff0 = float{mEarlyDelayCoeff[0]}; + const auto earlycoeff1 = float{mEarlyDelayCoeff[1]}; + mEarlyDelayCoeff[0] = mEarlyDelayCoeff[1]; + for(size_t j{0_uz};j < NUM_LINES;j++) { - const float *input{in_delay.get(j).data()}; - size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; - size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; + const auto input = in_delay.get(j); + auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; + auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; - const float coeff0{mEarlyDelayCoeff[j][0]}; - const float coeff1{mEarlyDelayCoeff[j][1]}; - mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1]; - const float coeffStep{(coeff1-coeff0)*fadeStep}; - float fadeCount{0.0f}; + auto fadeCount = 0.0f; - for(size_t i{0u};i < todo;) + auto tmp = tempSamples[j].begin(); + for(size_t i{0_uz};i < todo;) { - early_delay_tap0 &= in_delay.Mask; - early_delay_tap1 &= in_delay.Mask; - const size_t max_tap{std::max(early_delay_tap0, early_delay_tap1)}; - size_t td{std::min(in_delay.Mask+1 - max_tap, todo-i)}; - do { - tempSamples[j][i++] = lerpf(input[early_delay_tap0++]*coeff0, - input[early_delay_tap1++]*coeff1, coeffStep*fadeCount); + early_delay_tap0 &= input.size()-1; + early_delay_tap1 &= input.size()-1; + const auto max_tap = size_t{std::max(early_delay_tap0, early_delay_tap1)}; + const auto td = size_t{std::min(input.size()-max_tap, todo-i)}; + const auto intap0 = input.subspan(early_delay_tap0, td); + const auto intap1 = input.subspan(early_delay_tap1, td); + + auto do_blend = [earlycoeff0,earlycoeff1,fadeStep,&fadeCount](const float in0, + const float in1) noexcept -> float + { + const auto ret = lerpf(in0*earlycoeff0, in1*earlycoeff1, fadeStep*fadeCount); fadeCount += 1.0f; - } while(--td); + return ret; + }; + tmp = std::transform(intap0.begin(), intap0.end(), intap1.begin(), tmp, do_blend); + early_delay_tap0 += td; + early_delay_tap1 += td; + i += td; } /* Band-pass the incoming samples. */ auto&& filter = DualBiquad{mFilter[j].Lp, mFilter[j].Hp}; - filter.process({tempSamples[j].cbegin(), todo}, tempSamples[j]); + filter.process(al::span{tempSamples[j]}.first(todo), tempSamples[j]); } /* Apply an all-pass, to help color the initial reflections. */ mEarly.VecAp.process(tempSamples, offset, todo); - /* Apply a delay and bounce to generate secondary reflections, combine - * with the primary reflections and write out the result for mixing. - */ + /* Apply a delay and bounce to generate secondary reflections. */ early_delay.writeReflected(offset, tempSamples, todo); - for(size_t j{0u};j < NUM_LINES;j++) + const auto feedb_coeff = mEarly.Coeff; + for(size_t j{0_uz};j < NUM_LINES;j++) { - const float *input{early_delay.get(j).data()}; - size_t feedb_tap{offset - mEarly.Offset[j]}; - const float feedb_coeff{mEarly.Coeff[j]}; - float *RESTRICT out{al::assume_aligned<16>(outSamples[j].data() + base)}; + const auto input = early_delay.get(j); + auto feedb_tap = size_t{offset - mEarly.Offset[j]}; + auto out = outSamples[j].begin() + base; + auto tmp = tempSamples[j].begin(); - for(size_t i{0u};i < todo;) + for(size_t i{0_uz};i < todo;) { - feedb_tap &= early_delay.Mask; - size_t td{std::min(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - float sample{input[feedb_tap++]}; - out[i] = tempSamples[j][i] + sample*feedb_coeff; - tempSamples[j][i] = sample; - ++i; - } while(--td); + feedb_tap &= input.size()-1; + + const auto td = size_t{std::min(input.size() - feedb_tap, todo - i)}; + const auto delaySrc = input.subspan(feedb_tap, td); + + /* Combine the main input with the attenuated delayed echo for + * the early output. + */ + out = std::transform(delaySrc.begin(), delaySrc.end(), tmp, out, + [feedb_coeff](const float delayspl, const float mainspl) noexcept -> float + { return delayspl*feedb_coeff + mainspl; }); + + /* Move the (non-attenuated) delayed echo to the temp buffer + * for feeding the late reverb. + */ + tmp = std::copy_n(delaySrc.begin(), delaySrc.size(), tmp); + feedb_tap += td; + i += td; } } - /* Finally, write the result to the late delay line input for the late - * reverb stage to pick up at the appropriate time, applying a scatter - * and bounce to improve the initial diffusion in the late reverb. + /* Finally, apply a scatter and bounce to improve the initial diffusion + * in the late reverb, writing the result to the late delay line input. */ VectorScatterRev(mixX, mixY, tempSamples, todo); - for(size_t j{0u};j < NUM_LINES;j++) - mLateDelayIn.write(offset, j, tempSamples[j].data(), todo); + for(size_t j{0_uz};j < NUM_LINES;j++) + mLateDelayIn.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; } } -void Modulation::calcDelays(size_t todo) +auto Modulation::calcDelays(size_t todo) -> al::span { - uint idx{Index}; - const uint step{Step}; - const float depth{Depth * float{gCubicTable.sTableSteps}}; - for(size_t i{0};i < todo;++i) + auto idx = Index; + const auto step = Step; + const auto depth = Depth * float{gCubicTable.sTableSteps}; + const auto delays = al::span{ModDelays}.first(todo); + std::generate(delays.begin(), delays.end(), [step,depth,&idx] { idx += step; - const float x{static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE)}; + 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 float lfo{!(idx&(MOD_FRACONE>>1)) + 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)}; - ModDelays[i] = float2uint((lfo+1.0f) * depth); - } + : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f); + return float2uint((lfo+1.0f) * depth); + }); Index = idx; + return delays; } @@ -1637,7 +1642,7 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { @@ -1646,84 +1651,91 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, ASSUME(todo > 0); /* First, calculate the modulated delays for the late feedback. */ - mLate.Mod.calcDelays(todo); + const auto delays = mLate.Mod.calcDelays(todo); /* Now load samples from the feedback delay lines. Filter the signal to * apply its frequency-dependent decay. */ - for(size_t j{0u};j < NUM_LINES;++j) + for(size_t j{0_uz};j < NUM_LINES;++j) { - const float *input{late_delay.get(j).data()}; - size_t late_feedb_tap{offset - mLate.Offset[j]}; - const float midGain{mLate.T60[j].MidGain}; + const auto input = late_delay.get(j); + const auto midGain = mLate.T60[j].MidGain; + auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; - for(size_t i{0u};i < todo;++i) + auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float { - /* Calculate the read offset and offset between it and the next - * sample. + /* Calculate the read sample offset and sub-sample offset + * between it and the next sample. */ - const size_t idelay{mLate.Mod.ModDelays[i]}; - const size_t delay{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; - const size_t delayoffset{idelay & gCubicTable.sTableMask}; + const auto delay = late_feedb_tap - (idelay>>gCubicTable.sTableBits); + const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; ++late_feedb_tap; - /* Get the samples around by the delayed offset. */ - const float out0{input[(delay ) & late_delay.Mask]}; - const float out1{input[(delay-1) & late_delay.Mask]}; - const float out2{input[(delay-2) & late_delay.Mask]}; - const float out3{input[(delay-3) & late_delay.Mask]}; + /* Get the samples around the delayed offset, interpolated for + * output. + */ + const auto out0 = float{input[(delay ) & (input.size()-1)]}; + const auto out1 = float{input[(delay-1) & (input.size()-1)]}; + const auto out2 = float{input[(delay-2) & (input.size()-1)]}; + const auto out3 = float{input[(delay-3) & (input.size()-1)]}; - const float out{out0*gCubicTable.getCoeff0(delayoffset) + const auto out = out0*gCubicTable.getCoeff0(delayoffset) + out1*gCubicTable.getCoeff1(delayoffset) + out2*gCubicTable.getCoeff2(delayoffset) - + out3*gCubicTable.getCoeff3(delayoffset)}; - tempSamples[j][i] = out * midGain; - } + + out3*gCubicTable.getCoeff3(delayoffset); + return out * midGain; + }; + std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); - mLate.T60[j].process({tempSamples[j].data(), todo}); + mLate.T60[j].process(al::span{tempSamples[j]}.first(todo)); } /* Next load decorrelated samples from the main delay lines. */ const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;++j) + for(size_t j{0_uz};j < NUM_LINES;++j) { - const float *input{in_delay.get(j).data()}; - size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; - size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; - const float densityGain{mLate.DensityGain}; - const float densityStep{late_delay_tap0 != late_delay_tap1 ? - densityGain*fadeStep : 0.0f}; - float fadeCount{0.0f}; + const auto input = in_delay.get(j); + auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; + auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; + mLateDelayTap[j][0] = mLateDelayTap[j][1]; + const auto densityGain = mLate.DensityGain; + const auto densityStep = late_delay_tap0 != late_delay_tap1 + ? densityGain*fadeStep : 0.0f; + auto fadeCount = 0.0f; + auto samples = tempSamples[j].begin(); for(size_t i{0u};i < todo;) { - late_delay_tap0 &= in_delay.Mask; - late_delay_tap1 &= in_delay.Mask; - size_t td{std::min(in_delay.Mask+1 - std::max(late_delay_tap0, late_delay_tap1), - todo-i)}; - do { - const float fade0{densityGain - densityStep*fadeCount}; - const float fade1{densityStep*fadeCount}; + late_delay_tap0 &= input.size()-1; + late_delay_tap1 &= input.size()-1; + const auto td = size_t{std::min(todo - i, + input.size() - std::max(late_delay_tap0, late_delay_tap1))}; + + auto proc_sample = [input,densityGain,densityStep,&late_delay_tap0, + &late_delay_tap1,&fadeCount](const float sample) noexcept -> float + { + const auto fade0 = float{densityGain - densityStep*fadeCount}; + const auto fade1 = float{densityStep*fadeCount}; fadeCount += 1.0f; - tempSamples[j][i] += input[late_delay_tap0++]*fade0 + - input[late_delay_tap1++]*fade1; - ++i; - } while(--td); + return input[late_delay_tap0++]*fade0 + input[late_delay_tap1++]*fade1 + + sample; + }; + samples = std::transform(samples, samples+ptrdiff_t(td), samples, proc_sample); + i += td; } - mLateDelayTap[j][0] = mLateDelayTap[j][1]; } /* Apply a vector all-pass to improve micro-surface diffusion, and * write out the results for mixing. */ mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;++j) + for(size_t j{0_uz};j < NUM_LINES;++j) std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ VectorScatterRev(mixX, mixY, tempSamples, todo); - for(size_t j{0u};j < NUM_LINES;++j) - late_delay.write(offset, j, tempSamples[j].data(), todo); + for(size_t j{0_uz};j < NUM_LINES;++j) + late_delay.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; @@ -1734,7 +1746,7 @@ void ReverbState::process(const size_t samplesToDo, const al::span 0); + ASSUME(samplesToDo <= BufferLineSize); auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline]; @@ -1748,18 +1760,17 @@ void ReverbState::process(const size_t samplesToDo, const al::span(samplesIn[i].data())}; auto mix_sample = [gain](const float sample, const float in) noexcept -> float { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), mix_sample); + std::transform(tmpspan.begin(), tmpspan.end(), samplesIn[i].begin(), tmpspan.begin(), + mix_sample); } - mMainDelay.write(offset, c, tmpspan.data(), samplesToDo); + mMainDelay.write(offset, c, tmpspan); } - if(mPipelineState < Fading) - mPipelineState = Fading; + mPipelineState = std::max(Fading, mPipelineState); /* Process reverb for these samples. and mix them to the output. */ pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); @@ -1771,9 +1782,9 @@ void ReverbState::process(const size_t samplesToDo, const al::span{new ReverbState{}}; } }; -struct StdReverbStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new ReverbState{}}; } -}; - } // namespace EffectStateFactory *ReverbStateFactory_getFactory() @@ -1825,9 +1831,3 @@ EffectStateFactory *ReverbStateFactory_getFactory() static ReverbStateFactory ReverbFactory{}; return &ReverbFactory; } - -EffectStateFactory *StdReverbStateFactory_getFactory() -{ - static StdReverbStateFactory ReverbFactory{}; - return &ReverbFactory; -} diff --git a/libs/openal-soft/alc/effects/vmorpher.cpp b/libs/openal-soft/alc/effects/vmorpher.cpp index b07e6244..d9141bb6 100644 --- a/libs/openal-soft/alc/effects/vmorpher.cpp +++ b/libs/openal-soft/alc/effects/vmorpher.cpp @@ -249,7 +249,7 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float frequency{static_cast(device->Frequency)}; + const float frequency{static_cast(device->mSampleRate)}; const float step{props.Rate / frequency}; mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); @@ -286,6 +286,8 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { + alignas(16) std::array blended{}; + /* Following the EFX specification for a conformant implementation which describes * the effect as a pair of 4-band formant filters blended together using an LFO. */ @@ -324,12 +326,11 @@ void VmorpherState::process(const size_t samplesToDo, const al::span blended; for(size_t i{0u};i < td;i++) blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ - MixSamples({blended.data(), td}, samplesOut[outidx].data()+base, + MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base), chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base); ++chandata; } diff --git a/libs/openal-soft/alc/events.cpp b/libs/openal-soft/alc/events.cpp index 1010a338..dfabcf89 100644 --- a/libs/openal-soft/alc/events.cpp +++ b/libs/openal-soft/alc/events.cpp @@ -3,9 +3,11 @@ #include "events.h" +#include "alnumeric.h" #include "alspan.h" #include "core/logging.h" #include "device.h" +#include "fmt/core.h" namespace { @@ -19,7 +21,7 @@ ALCenum EnumFromEventType(const alc::EventType type) case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; case alc::EventType::Count: break; } - throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))}; + throw std::runtime_error{fmt::format("Invalid EventType: {}", int{al::to_underlying(type)})}; } } // namespace @@ -74,7 +76,7 @@ FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const AL auto etype = alc::GetEventType(type); if(!etype) { - WARN("Invalid event type: 0x%04x\n", type); + WARN("Invalid event type: {:#04x}", as_unsigned(type)); alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } diff --git a/libs/openal-soft/alc/export_list.h b/libs/openal-soft/alc/export_list.h index 23a74e62..b83f2c38 100644 --- a/libs/openal-soft/alc/export_list.h +++ b/libs/openal-soft/alc/export_list.h @@ -1,12 +1,14 @@ #ifndef ALC_EXPORT_LIST_H #define ALC_EXPORT_LIST_H +#include "config.h" + #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "inprogext.h" -#ifdef ALSOFT_EAX +#if ALSOFT_EAX #include "context.h" #include "al/eax/x_ram.h" #endif @@ -202,11 +204,6 @@ inline const FuncExport alcFunctions[]{ DECL(alGetBuffer3PtrSOFT), DECL(alGetBufferPtrvSOFT), - DECL(alAuxiliaryEffectSlotPlaySOFT), - DECL(alAuxiliaryEffectSlotPlayvSOFT), - DECL(alAuxiliaryEffectSlotStopSOFT), - DECL(alAuxiliaryEffectSlotStopvSOFT), - DECL(alSourcePlayAtTimeSOFT), DECL(alSourcePlayAtTimevSOFT), @@ -220,6 +217,10 @@ inline const FuncExport alcFunctions[]{ DECL(alPushDebugGroupEXT), DECL(alPopDebugGroupEXT), DECL(alGetDebugMessageLogEXT), + DECL(alObjectLabelEXT), + DECL(alGetObjectLabelEXT), + DECL(alGetPointerEXT), + DECL(alGetPointervEXT), /* Direct Context functions */ DECL(alcGetProcAddress2), @@ -370,15 +371,15 @@ inline const FuncExport alcFunctions[]{ DECL(alPushDebugGroupDirectEXT), DECL(alPopDebugGroupDirectEXT), DECL(alGetDebugMessageLogDirectEXT), - DECL(alObjectLabelEXT), DECL(alObjectLabelDirectEXT), - DECL(alGetObjectLabelEXT), DECL(alGetObjectLabelDirectEXT), + DECL(alGetPointerDirectEXT), + DECL(alGetPointervDirectEXT), /* Extra functions */ DECL(alsoft_set_log_callback), }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX inline const std::array eaxFunctions{ DECL(EAXGet), DECL(EAXSet), @@ -632,6 +633,8 @@ inline const EnumExport alcEnumerations[]{ 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), @@ -908,7 +911,7 @@ inline const EnumExport alcEnumerations[]{ DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), }; -#ifdef ALSOFT_EAX +#if ALSOFT_EAX inline const std::array eaxEnumerations{ DECL(AL_EAX_RAM_SIZE), DECL(AL_EAX_RAM_FREE), @@ -916,7 +919,7 @@ inline const std::array eaxEnumerations{ DECL(AL_STORAGE_HARDWARE), DECL(AL_STORAGE_ACCESSIBLE), }; -#endif // ALSOFT_EAX +#endif #undef DECL #endif /* ALC_EXPORT_LIST_H */ diff --git a/libs/openal-soft/alc/inprogext.h b/libs/openal-soft/alc/inprogext.h index 925ff8bd..eb421fd6 100644 --- a/libs/openal-soft/alc/inprogext.h +++ b/libs/openal-soft/alc/inprogext.h @@ -37,26 +37,11 @@ void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffe #endif #endif -#ifndef AL_SOFT_bformat_hoa -#define AL_SOFT_bformat_hoa -#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D -#endif - #ifndef AL_SOFT_convolution_effect #define AL_SOFT_convolution_effect #define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */ #define AL_EFFECTSLOT_STATE_SOFT 0x199E -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid) AL_API_NOEXCEPT17; -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17; -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid) AL_API_NOEXCEPT17; -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17; -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT; -#endif #endif #ifndef AL_SOFT_hold_on_disconnect @@ -80,15 +65,18 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #define AL_FORMAT_71CHN_I32 0x19E5 #define AL_FORMAT_71CHN_FLOAT32 0x19E6 -#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7 -#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8 -#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9 +#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 0x19EA -#define AL_PAN_SOFT 0x19EB +#define AL_PANNING_ENABLED_SOFT 0x19EC +#define AL_PAN_SOFT 0x19ED #endif /* Non-standard exports. Not part of any extension. */ @@ -101,6 +89,11 @@ void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *us 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; diff --git a/libs/openal-soft/alc/panning.cpp b/libs/openal-soft/alc/panning.cpp index bf719384..bf0736de 100644 --- a/libs/openal-soft/alc/panning.cpp +++ b/libs/openal-soft/alc/panning.cpp @@ -26,10 +26,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -42,7 +42,6 @@ #include "AL/alext.h" #include "alc/context.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" @@ -77,48 +76,77 @@ using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; -inline const char *GetLabelFromChannel(Channel channel) +[[nodiscard]] +auto GetLabelFromChannel(Channel channel) -> std::string_view { switch(channel) { - case FrontLeft: return "front-left"; - case FrontRight: return "front-right"; - case FrontCenter: return "front-center"; - case LFE: return "lfe"; - case BackLeft: return "back-left"; - case BackRight: return "back-right"; - case BackCenter: return "back-center"; - case SideLeft: return "side-left"; - case SideRight: return "side-right"; + 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"; - case TopFrontCenter: return "top-front-center"; - case TopFrontRight: return "top-front-right"; - case TopCenter: return "top-center"; - case TopBackLeft: return "top-back-left"; - case TopBackCenter: return "top-back-center"; - case TopBackRight: return "top-back-right"; + 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 Aux0: return "Aux0"; - case Aux1: return "Aux1"; - case Aux2: return "Aux2"; - case Aux3: return "Aux3"; - case Aux4: return "Aux4"; - case Aux5: return "Aux5"; - case Aux6: return "Aux6"; - case Aux7: return "Aux7"; - case Aux8: return "Aux8"; - case Aux9: return "Aux9"; - case Aux10: return "Aux10"; - case Aux11: return "Aux11"; - case Aux12: return "Aux12"; - case Aux13: return "Aux13"; - case Aux14: return "Aux14"; - case Aux15: return "Aux15"; + 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 MaxChannels: break; + 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)"; + return "(unknown)"sv; +} + +[[nodiscard]] +auto GetLayoutName(DevAmbiLayout layout) noexcept -> std::string_view +{ + switch(layout) + { + case DevAmbiLayout::FuMa: return "FuMa"sv; + case DevAmbiLayout::ACN: return "ACN"sv; + } + return ""sv; +} + +[[nodiscard]] +auto GetScalingName(DevAmbiScaling scaling) noexcept -> std::string_view +{ + switch(scaling) + { + case DevAmbiScaling::FuMa: return "FuMa"sv; + case DevAmbiScaling::SN3D: return "SN3D"sv; + case DevAmbiScaling::N3D: return "N3D"sv; + } + return ""sv; } @@ -136,14 +164,14 @@ std::unique_ptr CreateStablizer(const size_t outchans, const uin return stablizer; } -void AllocChannels(ALCdevice *device, const size_t main_chans, const size_t real_chans) +void AllocChannels(al::Device *device, const size_t main_chans, const size_t real_chans) { - TRACE("Channel config, Main: %zu, Real: %zu\n", main_chans, real_chans); + TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans); /* Allocate extra channels for any post-filter output. */ const size_t num_chans{main_chans + real_chans}; - TRACE("Allocating %zu channels, %zu bytes\n", num_chans, + TRACE("Allocating {} channels, {} bytes", num_chans, num_chans*sizeof(device->MixBuffer[0])); device->MixBuffer.resize(num_chans); al::span buffer{device->MixBuffer}; @@ -235,7 +263,8 @@ struct DecoderConfig { using DecoderView = DecoderConfig; -void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d) +void InitNearFieldCtrl(al::Device *device, const float ctrl_dist, const uint order, + const bool is3d) { static const std::array chans_per_order2d{{1, 2, 2, 2}}; static const std::array chans_per_order3d{{1, 3, 5, 7}}; @@ -245,10 +274,10 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde return; device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); - TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); + TRACE("Using near-field reference distance: {:.2f} meters", device->AvgSpeakerDist); const float w1{SpeedOfSoundMetersPerSec / - (device->AvgSpeakerDist * static_cast(device->Frequency))}; + (device->AvgSpeakerDist * static_cast(device->mSampleRate))}; device->mNFCtrlFilter.init(w1); auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, @@ -256,7 +285,7 @@ void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint orde std::fill(iter, device->NumChannelsPerOrder.end(), 0u); } -void InitDistanceComp(ALCdevice *device, const al::span channels, +void InitDistanceComp(al::Device *device, const al::span channels, const al::span dists) { const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, @@ -265,7 +294,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; - const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; + const auto distSampleScale = static_cast(device->mSampleRate)/SpeedOfSoundMetersPerSec; struct DistCoeffs { uint Length{}; float Gain{}; }; std::vector ChanDelay; @@ -290,7 +319,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx, + ERR("Delay for channel {} ({}) exceeds buffer length ({:f} > {})", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = float{DistanceComp::MaxDelay-1}; } @@ -298,7 +327,7 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay[idx].Length = static_cast(delay); ChanDelay[idx].Gain = distance / maxdist; - TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), + 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 @@ -341,8 +370,8 @@ constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept } -DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, - DecoderConfig &decoder) +auto MakeDecoderView(al::Device *device, const AmbDecConf *conf, + DecoderConfig &decoder) -> DecoderView { DecoderView ret{}; @@ -372,7 +401,7 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, const auto lfmatrix = conf->LFMatrix; uint chan_count{0}; - for(auto &speaker : al::span{conf->Speakers}) + for(auto &speaker : al::span{std::as_const(conf->Speakers)}) { /* NOTE: AmbDec does not define any standard speaker names, however * for this to work we have to by able to find the output channel @@ -391,44 +420,57 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, * RFT = Top front right * LBT = Top back left * RBT = Top back right + * LFB = Bottom front left + * RFB = Bottom front right + * LBB = Bottom back left + * RBB = Bottom back right * * Additionally, surround51 will acknowledge back speakers for side * channels, to avoid issues with an ambdec expecting 5.1 to use the * back channels. */ Channel ch{}; - if(speaker.Name == "LF") + if(speaker.Name == "LF"sv) ch = FrontLeft; - else if(speaker.Name == "RF") + else if(speaker.Name == "RF"sv) ch = FrontRight; - else if(speaker.Name == "CE") + else if(speaker.Name == "CE"sv) ch = FrontCenter; - else if(speaker.Name == "LS") + else if(speaker.Name == "LS"sv) ch = SideLeft; - else if(speaker.Name == "RS") + else if(speaker.Name == "RS"sv) ch = SideRight; - else if(speaker.Name == "LB") + else if(speaker.Name == "LB"sv) ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; - else if(speaker.Name == "RB") + else if(speaker.Name == "RB"sv) ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; - else if(speaker.Name == "CB") + else if(speaker.Name == "CB"sv) ch = BackCenter; - else if(speaker.Name == "LFT") + else if(speaker.Name == "LFT"sv) ch = TopFrontLeft; - else if(speaker.Name == "RFT") + else if(speaker.Name == "RFT"sv) ch = TopFrontRight; - else if(speaker.Name == "LBT") + else if(speaker.Name == "LBT"sv) ch = TopBackLeft; - else if(speaker.Name == "RBT") + else if(speaker.Name == "RBT"sv) ch = TopBackRight; + else if(speaker.Name == "LFB"sv) + ch = BottomFrontLeft; + else if(speaker.Name == "RFB"sv) + ch = BottomFrontRight; + else if(speaker.Name == "LBB"sv) + ch = BottomBackLeft; + else if(speaker.Name == "RBB"sv) + ch = BottomBackRight; else { int idx{}; char c{}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 || idx >= MaxChannels-Aux0) { - ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name); continue; } ch = static_cast(Aux0+idx); @@ -594,8 +636,46 @@ constexpr DecoderConfig X714Config{ {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, }} }; +constexpr DecoderConfig X7144Config{ + 1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{2.64575131e+0f, 1.52752523e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }} +}; -void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, +void InitPanning(al::Device *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) { if(!decoder) @@ -609,6 +689,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= 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. */ @@ -627,10 +708,13 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= avg_dist = *distopt; else if(auto delayopt = device->configValue("decoder", "nfc-ref-delay")) { - WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n"); + WARN("nfc-ref-delay is deprecated, use speaker-dist instead"); avg_dist = *delayopt * SpeedOfSoundMetersPerSec; } + TRACE("{}{} order ambisonic output ({} layout, {} scaling)", device->mAmbiOrder, + GetCounterSuffix(device->mAmbiOrder), GetLayoutName(device->mAmbiLayout), + GetScalingName(device->mAmbiScale)); InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); return; } @@ -645,7 +729,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; if(idx == InvalidChannelIndex) { - ERR("Failed to find %s channel in device\n", + ERR("Failed to find {} channel in device", GetLabelFromChannel(decoder.mChannels[i])); continue; } @@ -673,8 +757,8 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= device->mAmbiOrder = decoder.mOrder; device->m2DMixing = !decoder.mIs3D; - const al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN.data() : - AmbiIndex::FromACN2D.data(), ambicount}; + const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount) + : al::span{AmbiIndex::FromACN2D}.first(ambicount); const auto coeffscale = GetAmbiScales(decoder.mScaling); std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), [coeffscale](const uint8_t &acn) noexcept @@ -701,22 +785,21 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= } if(!hasfc) { - stablizer = CreateStablizer(device->channelsFromFmt(), device->Frequency); - TRACE("Front stablizer enabled\n"); + stablizer = CreateStablizer(device->channelsFromFmt(), device->mSampleRate); + TRACE("Front stablizer enabled"); } } - TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", - !dual_band ? "single" : "dual", + TRACE("Enabling {}-band {}-order{} ambisonic decoder", !dual_band ? "single" : "dual", (decoder.mOrder > 3) ? "fourth" : (decoder.mOrder > 2) ? "third" : (decoder.mOrder > 1) ? "second" : "first", decoder.mIs3D ? " periphonic" : ""); device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, - device->mXOverFreq/static_cast(device->Frequency), std::move(stablizer)); + device->mXOverFreq/static_cast(device->mSampleRate), std::move(stablizer)); } -void InitHrtfPanning(ALCdevice *device) +void InitHrtfPanning(al::Device *device) { static constexpr float Deg180{al::numbers::pi_v}; static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; @@ -874,7 +957,7 @@ void InitHrtfPanning(ALCdevice *device) std::string_view mode{*modeopt}; if(al::case_compare(mode, "basic"sv) == 0) { - ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2"); + ERR("HRTF mode \"{}\" deprecated, substituting \"{}\"", *modeopt, "ambi2"); mode = "ambi2"; } @@ -882,16 +965,16 @@ void InitHrtfPanning(ALCdevice *device) { return al::case_compare(mode, entry.name) == 0; }; auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); if(iter == hrtf_modes.end()) - ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str()); + ERR("Unexpected hrtf-mode: {}", *modeopt); else { device->mRenderMode = iter->mode; ambi_order = iter->order; } } - TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order, + TRACE("{}{} order {}HRTF rendering enabled, using \"{}\"", ambi_order, GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", - device->mHrtfName.c_str()); + device->mHrtfName); bool perHrirMin{false}; auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); @@ -928,7 +1011,7 @@ void InitHrtfPanning(ALCdevice *device) InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); } -void InitUhjPanning(ALCdevice *device) +void InitUhjPanning(al::Device *device) { /* UHJ is always 2D first-order. */ static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; @@ -941,11 +1024,21 @@ void InitUhjPanning(ALCdevice *device) [](const uint8_t &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 mixing 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 + * reference becomes inconsequential beyond that. + */ + const auto spkr_dist = ConfigValueFloat({}, "uhj"sv, "distance-ref"sv).value_or(2.0f); + InitNearFieldCtrl(device, spkr_dist, device->mAmbiOrder, !device->m2DMixing); } } // namespace -void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode) +void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; @@ -972,6 +1065,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalc_str()); + ERR("Failed to load layout file {}", config); + ERR(" {}", *err); return false; } if(conf.Speakers.size() > MaxOutputChannels) { - ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(), + ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(), MaxOutputChannels); return false; } if(conf.ChanMask > Ambi3OrderMask) { - ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, + ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask, Ambi3OrderMask); return false; } - TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans), - conf.Description.c_str()); + TRACE("Using {} decoder: \"{}\"", DevFmtChannelsString(device->FmtChans), + conf.Description); device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); decoder_store = std::make_unique>(); decoder = MakeDecoderView(device, &conf, *decoder_store); - const auto confspeakers = al::span{conf.Speakers.cbegin(), decoder.mChannels.size()}; + const auto confspeakers = al::span{std::as_const(conf.Speakers)} + .first(decoder.mChannels.size()); std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(), std::mem_fn(&AmbDecConf::SpeakerConf::Distance)); return true; @@ -1024,7 +1119,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalc_str()); } if(!usingCustom && device->FmtChans != DevFmtAmbi3D) - TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans)); + TRACE("Using built-in {} decoder", DevFmtChannelsString(device->FmtChans)); /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. @@ -1056,8 +1151,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalAmbiDecoder.get()}) { - device->PostProcess = ambidec->hasStablizer() ? &ALCdevice::ProcessAmbiDecStablized - : &ALCdevice::ProcessAmbiDec; + device->PostProcess = ambidec->hasStablizer() ? &al::Device::ProcessAmbiDecStablized + : &al::Device::ProcessAmbiDec; } return; } @@ -1075,7 +1170,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { const std::string_view hrtfname{device->mHrtfList[static_cast(hrtf_id)]}; - if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; @@ -1086,7 +1181,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalmHrtfList) { - if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) + if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; @@ -1108,7 +1203,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalPostProcess = &ALCdevice::ProcessHrtf; + device->PostProcess = &al::Device::ProcessHrtf; device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; return; } @@ -1117,23 +1212,27 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optionalmUhjEncoder = std::make_unique(); + ftype = "IIR"sv; break; case UhjQualityType::FIR256: device->mUhjEncoder = std::make_unique>(); + ftype = "FIR-256"sv; break; case UhjQualityType::FIR512: device->mUhjEncoder = std::make_unique>(); + ftype = "FIR-512"sv; break; } assert(device->mUhjEncoder != nullptr); - TRACE("UHJ enabled\n"); + TRACE("UHJ enabled ({} encoder)", ftype); InitUhjPanning(device); - device->PostProcess = &ALCdevice::ProcessUhj; + device->PostProcess = &al::Device::ProcessUhj; return; } @@ -1145,19 +1244,19 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional 0 && *cflevopt <= 6) { auto bs2b = std::make_unique(); - bs2b->set_params(*cflevopt, static_cast(device->Frequency)); + bs2b->set_params(*cflevopt, static_cast(device->mSampleRate)); device->Bs2b = std::move(bs2b); - TRACE("BS2B enabled\n"); + TRACE("BS2B enabled"); InitPanning(device); - device->PostProcess = &ALCdevice::ProcessBs2b; + device->PostProcess = &al::Device::ProcessBs2b; return; } } } - TRACE("Stereo rendering\n"); + TRACE("Stereo rendering"); InitPanning(device); - device->PostProcess = &ALCdevice::ProcessAmbiDec; + device->PostProcess = &al::Device::ProcessAmbiDec; } diff --git a/libs/openal-soft/alsoftrc.sample b/libs/openal-soft/alsoftrc.sample index 3ca93796..617fb617 100644 --- a/libs/openal-soft/alsoftrc.sample +++ b/libs/openal-soft/alsoftrc.sample @@ -179,7 +179,8 @@ # 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 -# cubic - extrapolates samples using a 4-point Gaussian filter +# 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- @@ -188,7 +189,11 @@ # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales -#resampler = cubic +# 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 @@ -232,14 +237,15 @@ ## 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. -#output-limiter = true +# noise. On by default for integer sample types, and off by default for +# floating-point. +#output-limiter = ## dither: -# Applies dithering on the final mix, for 8- and 16-bit output by default. -# This replaces the distortion created by nearest-value quantization with low- -# level whitenoise. -#dither = true +# 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 @@ -585,6 +591,13 @@ # 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 @@ -643,6 +656,11 @@ # 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!) @@ -684,3 +702,15 @@ ## 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 = diff --git a/libs/openal-soft/appveyor.yml b/libs/openal-soft/appveyor.yml index e86f7a30..ed0f07a3 100644 --- a/libs/openal-soft/appveyor.yml +++ b/libs/openal-soft/appveyor.yml @@ -1,8 +1,8 @@ -version: 1.23.1.{build} +version: 1.24.3.{build} environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - GEN: "Visual Studio 16 2019" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + GEN: "Visual Studio 17 2022" matrix: - ARCH: Win32 CFG: Release diff --git a/libs/openal-soft/cmake/FindFFmpeg.cmake b/libs/openal-soft/cmake/FindFFmpeg.cmake index 26ed4d2f..d243defd 100644 --- a/libs/openal-soft/cmake/FindFFmpeg.cmake +++ b/libs/openal-soft/cmake/FindFFmpeg.cmake @@ -80,8 +80,37 @@ macro(find_component _component _pkgconfig _library _header) ${PC_LIB${_component}_LIBRARY_DIRS} ) - set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number." FORCE) - set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS." FORCE) + 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}) diff --git a/libs/openal-soft/cmake/FindJACK.cmake b/libs/openal-soft/cmake/FindJACK.cmake index b72fe3f9..c85d0643 100644 --- a/libs/openal-soft/cmake/FindJACK.cmake +++ b/libs/openal-soft/cmake/FindJACK.cmake @@ -43,7 +43,7 @@ find_path(JACK_INCLUDE_DIR NAMES jack/jack.h DOC "The JACK include directory" ) -find_library(JACK_LIBRARY NAMES jack +find_library(JACK_LIBRARY NAMES jack jack64 DOC "The JACK library" ) diff --git a/libs/openal-soft/cmake/FindSndIO.cmake b/libs/openal-soft/cmake/FindSndIO.cmake new file mode 100644 index 00000000..eb4a2587 --- /dev/null +++ b/libs/openal-soft/cmake/FindSndIO.cmake @@ -0,0 +1,31 @@ +# - 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) diff --git a/libs/openal-soft/cmake/FindSoundIO.cmake b/libs/openal-soft/cmake/FindSoundIO.cmake deleted file mode 100644 index 10450254..00000000 --- a/libs/openal-soft/cmake/FindSoundIO.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# - Find SoundIO (sndio) includes and libraries -# -# SOUNDIO_FOUND - True if SOUNDIO_INCLUDE_DIR & SOUNDIO_LIBRARY are -# found -# SOUNDIO_LIBRARIES - Set when SOUNDIO_LIBRARY is found -# SOUNDIO_INCLUDE_DIRS - Set when SOUNDIO_INCLUDE_DIR is found -# -# SOUNDIO_INCLUDE_DIR - where to find sndio.h, etc. -# SOUNDIO_LIBRARY - the sndio library -# - -find_path(SOUNDIO_INCLUDE_DIR - NAMES sndio.h - DOC "The SoundIO include directory" -) - -find_library(SOUNDIO_LIBRARY - NAMES sndio - DOC "The SoundIO library" -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SoundIO - REQUIRED_VARS SOUNDIO_LIBRARY SOUNDIO_INCLUDE_DIR -) - -if(SOUNDIO_FOUND) - set(SOUNDIO_LIBRARIES ${SOUNDIO_LIBRARY}) - set(SOUNDIO_INCLUDE_DIRS ${SOUNDIO_INCLUDE_DIR}) -endif() - -mark_as_advanced(SOUNDIO_INCLUDE_DIR SOUNDIO_LIBRARY) diff --git a/libs/openal-soft/common/alassert.cpp b/libs/openal-soft/common/alassert.cpp new file mode 100644 index 00000000..aa44841e --- /dev/null +++ b/libs/openal-soft/common/alassert.cpp @@ -0,0 +1,44 @@ + +#include "alassert.h" + +#include +#include + +namespace { + +[[noreturn]] +void throw_error(const std::string &message) +{ + throw std::runtime_error{message}; +} + +} /* namespace */ + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept +{ + /* Throwing an exception that tries to leave a noexcept function will + * hopefully cause the system to provide info about the caught exception in + * an error dialog. At least on Linux, this results in the process printing + * + * terminate called after throwing an instance of 'std::runtime_error' + * what(): + * + * before terminating from a SIGABRT. Hopefully Windows and Mac will do the + * appropriate things with the message to alert the user about an abnormal + * termination. + */ + auto errstr = std::string{filename}; + errstr += ':'; + errstr += std::to_string(linenum); + errstr += ": "; + errstr += funcname; + errstr += ": "; + errstr += message; + + throw_error(errstr); +} + +} /* namespace al */ diff --git a/libs/openal-soft/common/alassert.h b/libs/openal-soft/common/alassert.h new file mode 100644 index 00000000..3797e96d --- /dev/null +++ b/libs/openal-soft/common/alassert.h @@ -0,0 +1,24 @@ +#ifndef AL_ASSERT_H +#define AL_ASSERT_H + +#include + +#include "opthelpers.h" + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept; + +} /* namespace al */ + +/* A custom assert macro that is not compiled out for Release/NDEBUG builds, + * making it an appropriate replacement for assert() checks that must not be + * ignored. + */ +#define alassert(cond) do { \ + if(!(cond)) UNLIKELY \ + al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \ +} while(0) + +#endif /* AL_ASSERT_H */ diff --git a/libs/openal-soft/common/albit.h b/libs/openal-soft/common/albit.h index 98544fa5..b5c10d71 100644 --- a/libs/openal-soft/common/albit.h +++ b/libs/openal-soft/common/albit.h @@ -1,6 +1,7 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include #include #ifndef __GNUC__ #include @@ -25,6 +26,16 @@ To> bit_cast(const From &src) noexcept return *std::launder(reinterpret_cast(dst.data())); } +template +std::enable_if_t, +T> byteswap(T value) noexcept +{ + static_assert(std::has_unique_object_representations_v); + auto bytes = al::bit_cast>(value); + std::reverse(bytes.begin(), bytes.end()); + return al::bit_cast(bytes); +} + #ifdef __BYTE_ORDER__ enum class endian { little = __ORDER_LITTLE_ENDIAN__, diff --git a/libs/openal-soft/common/alcomplex.cpp b/libs/openal-soft/common/alcomplex.cpp index 954c5fad..ae8bc41c 100644 --- a/libs/openal-soft/common/alcomplex.cpp +++ b/libs/openal-soft/common/alcomplex.cpp @@ -20,7 +20,7 @@ namespace { using ushort = unsigned short; -using ushort2 = std::pair; +using ushort2 = std::array; using complex_d = std::complex; constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept @@ -56,8 +56,8 @@ struct BitReverser { if(idx < revidx) { - mData[ret_i].first = static_cast(idx); - mData[ret_i].second = static_cast(revidx); + mData[ret_i][0] = static_cast(idx); + mData[ret_i][1] = static_cast(revidx); ++ret_i; } } @@ -122,7 +122,7 @@ void complex_fft(const al::span> buffer, const double sign) if(log2_size < gBitReverses.size()) LIKELY { for(auto &rev : gBitReverses[log2_size]) - std::swap(buffer[rev.first], buffer[rev.second]); + std::swap(buffer[rev[0]], buffer[rev[1]]); /* Iterative form of Danielson-Lanczos lemma */ for(std::size_t i{0};i < log2_size;++i) diff --git a/libs/openal-soft/common/almalloc.h b/libs/openal-soft/common/almalloc.h index 8ced0871..bd30fe15 100644 --- a/libs/openal-soft/common/almalloc.h +++ b/libs/openal-soft/common/almalloc.h @@ -12,7 +12,7 @@ namespace gsl { template using owner = T; -}; +} #define DISABLE_ALLOC \ @@ -88,6 +88,9 @@ constexpr bool operator!=(const allocator&, const allocator&) noexcept { return allocator::Alignment != allocator::Alignment; } +#ifdef __cpp_lib_to_address +using std::to_address; +#else template constexpr T *to_address(T *p) noexcept { @@ -100,7 +103,7 @@ constexpr auto to_address(const T &p) noexcept { return ::al::to_address(p.operator->()); } - +#endif template constexpr T* construct_at(T *ptr, Args&& ...args) @@ -120,31 +123,74 @@ class out_ptr_t { static_assert(!std::is_same_v); SP &mRes; - std::variant mPtr{}; + std::variant mPtr; public: - out_ptr_t(SP &res) : mRes{res} { } - ~out_ptr_t() - { - auto set_res = [this](auto &ptr) - { mRes.reset(static_cast(ptr)); }; - std::visit(set_res, mPtr); - } + 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 + operator PT*() noexcept /* NOLINT(google-explicit-constructor) */ { return &std::get(mPtr); } - operator void**() noexcept + operator void**() noexcept /* NOLINT(google-explicit-constructor) */ { return &mPtr.template emplace(); } }; template -auto out_ptr(SP &res) +auto out_ptr(SP &res, Args&& ...args) { - using ptype = typename SP::element_type*; - return out_ptr_t{res}; + static_assert(sizeof...(args) == 0); + if constexpr(std::is_same_v) + { + using ptype = typename 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 = typename SP::element_type*; + return inout_ptr_t{res}; + } + else + return inout_ptr_t{res}; } } // namespace al diff --git a/libs/openal-soft/common/alnumeric.h b/libs/openal-soft/common/alnumeric.h index 0d091166..cd9c55cf 100644 --- a/libs/openal-soft/common/alnumeric.h +++ b/libs/openal-soft/common/alnumeric.h @@ -1,17 +1,19 @@ #ifndef AL_NUMERIC_H #define AL_NUMERIC_H +#include "config_simd.h" + #include #include #include #include #include -#include +#include #include #ifdef HAVE_INTRIN_H #include #endif -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include #endif @@ -29,19 +31,27 @@ constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_ca constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast(n); } -constexpr auto GetCounterSuffix(size_t count) noexcept -> const char* +template,bool> = true> +constexpr auto as_unsigned(T value) noexcept { - auto &suffix = (((count%100)/10) == 1) ? "th" : - ((count%10) == 1) ? "st" : - ((count%10) == 2) ? "nd" : - ((count%10) == 3) ? "rd" : "th"; - return std::data(suffix); + using UT = std::make_unsigned_t; + return static_cast(value); } -constexpr inline float lerpf(float val1, float val2, float mu) noexcept +constexpr auto GetCounterSuffix(size_t count) noexcept -> std::string_view +{ + using namespace std::string_view_literals; + return (((count%100)/10) == 1) ? "th"sv : + ((count%10) == 1) ? "st"sv : + ((count%10) == 2) ? "nd"sv : + ((count%10) == 3) ? "rd"sv : "th"sv; +} + + +constexpr auto lerpf(float val1, float val2, float mu) noexcept -> float { return val1 + (val2-val1)*mu; } -constexpr inline double lerpd(double val1, double val2, double mu) noexcept +constexpr auto lerpd(double val1, double val2, double mu) noexcept -> double { return val1 + (val2-val1)*mu; } @@ -84,7 +94,7 @@ constexpr T RoundUp(T value, al::type_identity_t r) noexcept */ inline int fastf2i(float f) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvt_ss2si(_mm_set_ss(f)); #elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0 @@ -112,7 +122,7 @@ inline unsigned int fastf2u(float f) noexcept /** Converts float-to-int using standard behavior (truncation). */ inline int float2int(float f) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvtt_ss2si(_mm_set_ss(f)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ @@ -143,7 +153,7 @@ inline unsigned int float2uint(float f) noexcept /** Converts double-to-int using standard behavior (truncation). */ inline int double2int(double d) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS return _mm_cvttsd_si32(_mm_set_sd(d)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ @@ -241,7 +251,7 @@ inline float level_mb_to_gain(float x) // Converts gain to level (mB). inline float gain_to_level_mb(float x) { - if (x <= 0.0f) + if(x <= 1e-05f) return -10'000.0f; return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } diff --git a/libs/openal-soft/common/alsem.cpp b/libs/openal-soft/common/alsem.cpp index 6a92b35c..2eeed592 100644 --- a/libs/openal-soft/common/alsem.cpp +++ b/libs/openal-soft/common/alsem.cpp @@ -24,8 +24,6 @@ #include -#include "opthelpers.h" - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -39,7 +37,8 @@ semaphore::semaphore(unsigned int initial) { if(initial > static_cast(std::numeric_limits::max())) throw std::system_error(std::make_error_code(std::errc::value_too_large)); - mSem = CreateSemaphore(nullptr, initial, std::numeric_limits::max(), nullptr); + mSem = CreateSemaphoreW(nullptr, static_cast(initial), std::numeric_limits::max(), + nullptr); if(mSem == nullptr) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } diff --git a/libs/openal-soft/common/alsem.h b/libs/openal-soft/common/alsem.h index 90b39319..b23a54a1 100644 --- a/libs/openal-soft/common/alsem.h +++ b/libs/openal-soft/common/alsem.h @@ -27,7 +27,7 @@ class semaphore { native_type mSem{}; public: - semaphore(unsigned int initial=0); + explicit semaphore(unsigned int initial=0); semaphore(const semaphore&) = delete; ~semaphore(); diff --git a/libs/openal-soft/common/alspan.h b/libs/openal-soft/common/alspan.h index 3a78b9f1..33587e6e 100644 --- a/libs/openal-soft/common/alspan.h +++ b/libs/openal-soft/common/alspan.h @@ -9,6 +9,7 @@ #include #include +#include "alassert.h" #include "almalloc.h" #include "altraits.h" @@ -141,6 +142,9 @@ namespace detail_ { #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true +/* NOLINTBEGIN(google-explicit-constructor) This largely follows std::span's + * constructor behavior, and should be replaced once C++20 is used. + */ template class span { public: @@ -164,12 +168,11 @@ public: template constexpr span() noexcept { } template - constexpr explicit span(U iter, size_type size_ [[maybe_unused]]) - : mData{::al::to_address(iter)} - { assert(size_ == extent); } + constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)} + { alassert(size_ == extent); } template::value)> - constexpr explicit span(U first, V last [[maybe_unused]]) : mData{::al::to_address(first)} - { assert(static_cast(last-first) == extent); } + constexpr explicit span(U first, V last) : mData{::al::to_address(first)} + { alassert(static_cast(last-first) == extent); } template constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ @@ -188,7 +191,7 @@ public: template::value && detail_::is_array_compatible && N == dynamic_extent)> constexpr explicit span(const span &span_) noexcept : mData{std::data(span_)} - { assert(std::size(span_) == extent); } + { alassert(std::size(span_) == extent); } template::value && detail_::is_array_compatible && N == extent)> constexpr span(const span &span_) noexcept : mData{std::data(span_)} { } @@ -212,22 +215,24 @@ public: [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; } - [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } - [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } + [[nodiscard]] constexpr + auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } template - [[nodiscard]] constexpr auto first() const -> span + [[nodiscard]] constexpr auto first() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData, C}; } template - [[nodiscard]] constexpr auto last() const -> span + [[nodiscard]] constexpr auto last() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData+(E-C), C}; @@ -235,7 +240,7 @@ public: template [[nodiscard]] constexpr - auto subspan() const -> std::enable_if_t> + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); static_assert(E-O >= C, "New size exceeds original capacity"); @@ -244,7 +249,7 @@ public: template [[nodiscard]] constexpr - auto subspan() const -> std::enable_if_t> + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); return span{mData+O, E-O}; @@ -254,12 +259,13 @@ public: * defining the specialization. As a result, these methods need to be * defined later. */ - [[nodiscard]] constexpr auto first(std::size_t count) const + [[nodiscard]] constexpr + auto first(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto last(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span; - [[nodiscard]] constexpr auto last(std::size_t count) const - -> span; - [[nodiscard]] constexpr auto subspan(std::size_t offset, - std::size_t count=dynamic_extent) const -> span; private: pointer mData{nullptr}; @@ -335,72 +341,65 @@ public: [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; } - [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } - [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } + [[nodiscard]] constexpr + auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } template - [[nodiscard]] constexpr auto first() const -> span + [[nodiscard]] constexpr auto first() const noexcept -> span { - if(C > mDataLength) - throw std::out_of_range{"Subspan count out of range"}; + assert(C <= mDataLength); return span{mData, C}; } - [[nodiscard]] constexpr auto first(std::size_t count) const -> span + [[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span { - if(count > mDataLength) - throw std::out_of_range{"Subspan count out of range"}; + assert(count <= mDataLength); return span{mData, count}; } template - [[nodiscard]] constexpr auto last() const -> span + [[nodiscard]] constexpr auto last() const noexcept -> span { - if(C > mDataLength) - throw std::out_of_range{"Subspan count out of range"}; + assert(C <= mDataLength); return span{mData+mDataLength-C, C}; } - [[nodiscard]] constexpr auto last(std::size_t count) const -> span + [[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span { - if(count > mDataLength) - throw std::out_of_range{"Subspan count out of range"}; + assert(count <= mDataLength); return span{mData+mDataLength-count, count}; } template [[nodiscard]] constexpr - auto subspan() const -> std::enable_if_t> + auto subspan() const noexcept -> std::enable_if_t> { - if(O > mDataLength) - throw std::out_of_range{"Subspan offset out of range"}; - if(C > mDataLength-O) - throw std::out_of_range{"Subspan length out of range"}; + assert(O <= mDataLength); + assert(C <= mDataLength-O); return span{mData+O, C}; } template [[nodiscard]] constexpr - auto subspan() const -> std::enable_if_t> + auto subspan() const noexcept -> std::enable_if_t> { - if(O > mDataLength) - throw std::out_of_range{"Subspan offset out of range"}; + assert(O <= mDataLength); return span{mData+O, mDataLength-O}; } [[nodiscard]] constexpr - auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const -> span + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span { - if(offset > mDataLength) - throw std::out_of_range{"Subspan offset out of range"}; + assert(offset <= mDataLength); if(count != dynamic_extent) { - if(count > mDataLength-offset) - throw std::out_of_range{"Subspan length out of range"}; + assert(count <= mDataLength-offset); return span{mData+offset, count}; } return span{mData+offset, mDataLength-offset}; @@ -413,38 +412,34 @@ private: template [[nodiscard]] constexpr -auto span::first(std::size_t count) const -> span +auto span::first(std::size_t count) const noexcept -> span { - if(count > size()) - throw std::out_of_range{"Subspan count out of range"}; + assert(count <= size()); return span{mData, count}; } template [[nodiscard]] constexpr -auto span::last(std::size_t count) const -> span +auto span::last(std::size_t count) const noexcept -> span { - if(count > size()) - throw std::out_of_range{"Subspan count out of range"}; + assert(count <= size()); return span{mData+size()-count, count}; } template [[nodiscard]] constexpr -auto span::subspan(std::size_t offset, std::size_t count) const +auto span::subspan(std::size_t offset, std::size_t count) const noexcept -> span { - if(offset > size()) - throw std::out_of_range{"Subspan offset out of range"}; + assert(offset <= size()); if(count != dynamic_extent) { - if(count > size()-offset) - throw std::out_of_range{"Subspan length out of range"}; + assert(count <= size()-offset); return span{mData+offset, count}; } return span{mData+offset, size()-offset}; } - +/* NOLINTEND(google-explicit-constructor) */ template span(T, EndOrSize) -> span())>>; diff --git a/libs/openal-soft/common/alstring.cpp b/libs/openal-soft/common/alstring.cpp index da3c1756..d609823d 100644 --- a/libs/openal-soft/common/alstring.cpp +++ b/libs/openal-soft/common/alstring.cpp @@ -5,8 +5,8 @@ #include #include +#include #include -#include namespace al { @@ -31,13 +31,24 @@ int case_compare(const std::string_view str0, const std::string_view str1) noexc return 0; } -int strcasecmp(const char *str0, const char *str1) noexcept -{ return case_compare(str0, str1); } - -int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept { - return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)}, - std::string_view{str1, std::min(std::strlen(str1), len)}); + using Traits = std::wstring_view::traits_type; + + auto ch0 = str0.cbegin(); + auto ch1 = str1.cbegin(); + auto ch1end = ch1 + std::min(str0.size(), str1.size()); + while(ch1 != ch1end) + { + const auto u0 = std::towupper(Traits::to_int_type(*ch0)); + const auto u1 = std::towupper(Traits::to_int_type(*ch1)); + if(const auto diff = static_cast(u0-u1)) return diff; + ++ch0; ++ch1; + } + + if(str0.size() < str1.size()) return -1; + if(str0.size() > str1.size()) return 1; + return 0; } } // namespace al diff --git a/libs/openal-soft/common/alstring.h b/libs/openal-soft/common/alstring.h index 7ac8bfd1..c0393613 100644 --- a/libs/openal-soft/common/alstring.h +++ b/libs/openal-soft/common/alstring.h @@ -10,11 +10,17 @@ namespace al { -template +template [[nodiscard]] constexpr -auto sizei(const std::basic_string_view str) noexcept -> int +auto sizei(const std::basic_string_view str) noexcept -> int { return static_cast(std::min(str.size(), std::numeric_limits::max())); } +template +[[nodiscard]] constexpr +auto sizei(const std::basic_string &str) noexcept -> int +{ return static_cast(std::min(str.size(), std::numeric_limits::max())); } + + [[nodiscard]] constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept { return str0.find(str1) != std::string_view::npos; } @@ -31,9 +37,22 @@ constexpr bool ends_with(const std::string_view str0, const std::string_view str int case_compare(const std::string_view str0, const std::string_view str1) noexcept; [[nodiscard]] -int strcasecmp(const char *str0, const char *str1) noexcept; -[[nodiscard]] -int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept; +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept; + +/* C++20 changes path::u8string() to return a string using a new/distinct + * char8_t type for UTF-8 strings. However, support for this with standard + * string functions is totally inadequate, and we already hold UTF-8 with plain + * char strings. So this function is used to reinterpret a char8_t string as a + * char string_view. + */ +#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L +inline auto u8_as_char(const std::u8string_view str) -> std::string_view +#else +inline auto u8_as_char(const std::string_view str) -> std::string_view +#endif +{ + return std::string_view{reinterpret_cast(str.data()), str.size()}; +} } // namespace al diff --git a/libs/openal-soft/common/althreads.h b/libs/openal-soft/common/althreads.h index 96cf024f..66e62e55 100644 --- a/libs/openal-soft/common/althreads.h +++ b/libs/openal-soft/common/althreads.h @@ -9,7 +9,7 @@ #define WIN32_LEAN_AND_MEAN #include -#elif defined(__APPLE__) +#elif defined(__STDC_NO_THREADS__) || !__has_include() #include @@ -55,7 +55,7 @@ class tss { } #ifdef _WIN32 - DWORD mTss; + DWORD mTss{TLS_OUT_OF_INDEXES}; public: tss() : mTss{TlsAlloc()} @@ -79,9 +79,9 @@ public: [[nodiscard]] auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); } -#elif defined(__APPLE__) +#elif defined(__STDC_NO_THREADS__) || !__has_include() - pthread_key_t mTss; + pthread_key_t mTss{}; public: tss() @@ -107,7 +107,7 @@ public: #else - tss_t mTss; + tss_t mTss{}; public: tss() diff --git a/libs/openal-soft/common/comptr.h b/libs/openal-soft/common/comptr.h index 5110a6c6..c592f1d0 100644 --- a/libs/openal-soft/common/comptr.h +++ b/libs/openal-soft/common/comptr.h @@ -1,11 +1,9 @@ #ifndef COMMON_COMPTR_H #define COMMON_COMPTR_H +#ifdef _WIN32 #include -#include -#include #include -#include #define WIN32_LEAN_AND_MEAN #include @@ -17,7 +15,7 @@ struct ComWrapper { ComWrapper(void *reserved, DWORD coinit) : mStatus{CoInitializeEx(reserved, coinit)} { } - ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) + explicit ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) : mStatus{CoInitializeEx(nullptr, coinit)} { } ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); } @@ -33,6 +31,7 @@ struct ComWrapper { } ComWrapper& operator=(const ComWrapper&) = delete; + [[nodiscard]] HRESULT status() const noexcept { return mStatus; } explicit operator bool() const noexcept { return SUCCEEDED(status()); } @@ -45,7 +44,7 @@ struct ComWrapper { }; -template +template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ struct ComPtr { using element_type = T; @@ -56,10 +55,11 @@ struct ComPtr { 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 { } + ComPtr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } + /* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */ ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept) { if constexpr(RefIsNoexcept) @@ -107,5 +107,6 @@ struct ComPtr { private: T *mPtr{nullptr}; }; +#endif /* _WIN32 */ #endif diff --git a/libs/openal-soft/common/dynload.h b/libs/openal-soft/common/dynload.h index bd9e86f7..ce7c8f03 100644 --- a/libs/openal-soft/common/dynload.h +++ b/libs/openal-soft/common/dynload.h @@ -3,12 +3,16 @@ #if defined(_WIN32) || defined(HAVE_DLFCN_H) -#define HAVE_DYNLOAD +#define HAVE_DYNLOAD 1 void *LoadLib(const char *name); void CloseLib(void *handle); void *GetSymbol(void *handle, const char *name); +#else + +#define HAVE_DYNLOAD 0 + #endif #endif /* AL_DYNLOAD_H */ diff --git a/libs/openal-soft/common/filesystem.cpp b/libs/openal-soft/common/filesystem.cpp new file mode 100644 index 00000000..58da22a7 --- /dev/null +++ b/libs/openal-soft/common/filesystem.cpp @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------------------------- +// +// 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 diff --git a/libs/openal-soft/common/filesystem.h b/libs/openal-soft/common/filesystem.h new file mode 100644 index 00000000..8d556e87 --- /dev/null +++ b/libs/openal-soft/common/filesystem.h @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------------------------- +// +// 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 diff --git a/libs/openal-soft/common/flexarray.h b/libs/openal-soft/common/flexarray.h index 845e7ddd..b6f09c20 100644 --- a/libs/openal-soft/common/flexarray.h +++ b/libs/openal-soft/common/flexarray.h @@ -17,7 +17,7 @@ namespace al { * trivially destructible. */ template::value> -struct alignas(std::max(alignment, alignof(al::span))) FlexArrayStorage : al::span { +struct alignas(alignment) FlexArrayStorage : al::span { /* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the * sizeof(T) being suspicious when T is a pointer type, which it will be * for flexible arrays of pointers. @@ -30,7 +30,7 @@ struct alignas(std::max(alignment, alignof(al::span))) FlexArrayStorage : al: * arrays store their payloads after the end of the object, which must be * the last in the whole parent chain. */ - FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ @@ -41,12 +41,12 @@ struct alignas(std::max(alignment, alignof(al::span))) FlexArrayStorage : al: }; template -struct alignas(std::max(alignment, alignof(al::span))) FlexArrayStorage : al::span { +struct alignas(alignment) FlexArrayStorage : al::span { static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ - FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ @@ -73,7 +73,8 @@ struct FlexArray { using reference = T&; using const_reference = const T&; - using Storage_t_ = FlexArrayStorage; + static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)}; + using Storage_t_ = FlexArrayStorage), StorageAlign)>; using iterator = typename Storage_t_::iterator; using const_iterator = typename Storage_t_::const_iterator; @@ -87,7 +88,8 @@ struct FlexArray { static std::unique_ptr Create(index_type count) { return std::unique_ptr{new(FamCount{count}) FlexArray{count}}; } - FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v) + explicit FlexArray(index_type size) + noexcept(std::is_nothrow_constructible_v) : mStore{size} { } ~FlexArray() = default; diff --git a/libs/openal-soft/common/ghc_filesystem.h b/libs/openal-soft/common/ghc_filesystem.h new file mode 100644 index 00000000..b4848b36 --- /dev/null +++ b/libs/openal-soft/common/ghc_filesystem.h @@ -0,0 +1,6076 @@ +//--------------------------------------------------------------------------------------- +// +// 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 + diff --git a/libs/openal-soft/common/intrusive_ptr.h b/libs/openal-soft/common/intrusive_ptr.h index b1fa742f..1a446f44 100644 --- a/libs/openal-soft/common/intrusive_ptr.h +++ b/libs/openal-soft/common/intrusive_ptr.h @@ -15,6 +15,9 @@ template class intrusive_ref { std::atomic mRef{1u}; +protected: + ~intrusive_ref() = default; + public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } unsigned int dec_ref() noexcept @@ -48,7 +51,7 @@ public: }; -template +template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ class intrusive_ptr { T *mPtr{nullptr}; @@ -58,7 +61,7 @@ public: { if(mPtr) mPtr->add_ref(); } intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } - intrusive_ptr(std::nullptr_t) noexcept { } + intrusive_ptr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } diff --git a/libs/openal-soft/common/opthelpers.h b/libs/openal-soft/common/opthelpers.h index ae2611da..e22f220f 100644 --- a/libs/openal-soft/common/opthelpers.h +++ b/libs/openal-soft/common/opthelpers.h @@ -28,6 +28,24 @@ #define NOINLINE #endif +#if defined(__MINGW32__) && defined(__i386__) +/* 32-bit MinGW targets have a bug where __STDCPP_DEFAULT_NEW_ALIGNMENT__ + * reports 16, despite the default operator new calling standard malloc which + * only guarantees 8-byte alignment. As a result, structs that need and specify + * 16-byte alignment only get 8-byte alignment. Explicitly specifying 32-byte + * alignment forces the over-aligned operator new to be called, giving the + * correct (if larger than necessary) alignment. + * + * Technically this bug affects 32-bit GCC more generally, but typically only + * with fairly old glibc versions as newer versions do guarantee the 16-byte + * alignment as specified. MinGW is reliant on msvcrt.dll's malloc however, + * which can't be updated to give that guarantee. + */ +#define SIMDALIGN alignas(32) +#else +#define SIMDALIGN +#endif + /* Unlike the likely attribute, ASSUME requires the condition to be true or * else it invokes undefined behavior. It's essentially an assert without * actually checking the condition at run-time, allowing for stronger @@ -56,6 +74,12 @@ #define UNLIKELY #endif +#if !defined(_WIN32) && HAS_ATTRIBUTE(gnu::visibility) +#define DECL_HIDDEN [[gnu::visibility("hidden")]] +#else +#define DECL_HIDDEN +#endif + namespace al { template diff --git a/libs/openal-soft/common/pffft.cpp b/libs/openal-soft/common/pffft.cpp index fe09390c..07b6fbcc 100644 --- a/libs/openal-soft/common/pffft.cpp +++ b/libs/openal-soft/common/pffft.cpp @@ -73,6 +73,8 @@ #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "fmt/core.h" +#include "fmt/ranges.h" #include "opthelpers.h" @@ -80,6 +82,12 @@ 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 @@ -92,7 +100,8 @@ namespace { /* * Altivec support macros */ -#if defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__) +#if (defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__)) \ + && (defined(__VEC__) || defined(__ALTIVEC__)) #include using v4sf = vector float; constexpr uint SimdSize{4}; @@ -114,15 +123,13 @@ force_inline float vextract0(v4sf v) noexcept { return vec_extract(v, 0); } force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{vec_mergeh(in1, in2)}; + out1 = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); - out1 = tmp; } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27})}; + 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}); - out1 = tmp; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept @@ -169,15 +176,13 @@ force_inline float vextract0(v4sf v) noexcept force_inline void interleave2(const v4sf in1, const v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{_mm_unpacklo_ps(in1, in2)}; + out1 = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); - out1 = tmp; } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{_mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0))}; + out1 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); - out1 = tmp; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept @@ -277,15 +282,13 @@ force_inline v4sf unpackhi(v4sf a, v4sf b) noexcept force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{unpacklo(in1, in2)}; + out1 = unpacklo(in1, in2); out2 = unpackhi(in1, in2); - out1 = tmp; } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { - v4sf tmp{in1[0], in1[2], in2[0], in2[2]}; + out1 = v4sf{in1[0], in1[2], in2[0], in2[2]}; out2 = v4sf{in1[1], in1[3], in2[1], in2[3]}; - out1 = tmp; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept @@ -323,7 +326,7 @@ force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; } #else -[[maybe_unused]] inline +[[maybe_unused, nodiscard]] inline auto valigned(const float *ptr) noexcept -> bool { static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1}; @@ -357,61 +360,59 @@ constexpr auto make_float_array(std::integer_sequence) { return std::array{static_cast(N)...}; } /* detect bugs with the vector support macros */ -[[maybe_unused]] void validate_pffft_simd() +[[maybe_unused]] auto validate_pffft_simd() -> bool { using float4 = std::array; static constexpr auto f = make_float_array(std::make_index_sequence<16>{}); - v4sf a0_v{vset4(f[ 0], f[ 1], f[ 2], f[ 3])}; - v4sf a1_v{vset4(f[ 4], f[ 5], f[ 6], f[ 7])}; - v4sf a2_v{vset4(f[ 8], f[ 9], f[10], f[11])}; - v4sf a3_v{vset4(f[12], f[13], f[14], f[15])}; - v4sf u_v{}; + auto a0_v = vset4(f[ 0], f[ 1], f[ 2], f[ 3]); + auto a1_v = vset4(f[ 4], f[ 5], f[ 6], f[ 7]); + auto a2_v = vset4(f[ 8], f[ 9], f[10], f[11]); + auto a3_v = vset4(f[12], f[13], f[14], f[15]); auto t_v = vzero(); auto t_f = al::bit_cast(t_v); - printf("VZERO=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VZERO={}", t_f); assertv4(t_f, 0, 0, 0, 0); t_v = vadd(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VADD(4:7,8:11)={}", t_f); assertv4(t_f, 12, 14, 16, 18); t_v = vmul(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VMUL(4:7,8:11)={}", t_f); assertv4(t_f, 32, 45, 60, 77); t_v = vmadd(a1_v, a2_v, a0_v); t_f = al::bit_cast(t_v); - printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("VMADD(4:7,8:11,0:3)={}", t_f); assertv4(t_f, 32, 46, 62, 80); + auto u_v = v4sf{}; interleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); auto u_f = al::bit_cast(u_v); - printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + fmt::println("INTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 8, 5, 9); assertv4(u_f, 6, 10, 7, 11); uninterleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); u_f = al::bit_cast(u_v); - printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + fmt::println("UNINTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 6, 8, 10); assertv4(u_f, 5, 7, 9, 11); t_v = ld_ps1(f[15]); t_f = al::bit_cast(t_v); - printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + fmt::println("LD_PS1(15)={}", t_f); assertv4(t_f, 15, 15, 15, 15); t_v = vswaphl(a1_v, a2_v); t_f = al::bit_cast(t_v); - printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + 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); @@ -419,13 +420,13 @@ constexpr auto make_float_array(std::integer_sequence) auto a1_f = al::bit_cast(a1_v); auto a2_f = al::bit_cast(a2_v); auto a3_f = al::bit_cast(a3_v); - printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - a0_f[0], a0_f[1], a0_f[2], a0_f[3], a1_f[0], a1_f[1], a1_f[2], a1_f[3], - a2_f[0], a2_f[1], a2_f[2], a2_f[3], a3_f[0], a3_f[1], a3_f[2], a3_f[3]); + 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 @@ -434,6 +435,13 @@ constexpr auto make_float_array(std::integer_sequence) constexpr auto V4sfAlignment = size_t(64); constexpr auto V4sfAlignVal = std::align_val_t(V4sfAlignment); +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * FIXME: Converting this from raw pointers to spans or something will probably + * need significant work to maintain performance, given non-sequential range- + * checked accesses and lack of 'restrict' to indicate non-aliased memory. At + * least, some tests should be done to check the impact of using range-checked + * spans here before blindly switching. + */ /* passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 */ @@ -1051,7 +1059,7 @@ void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf * 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)); - //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4); + //fmt::println("pffft: radf5, k={} ch_ref={:f}, ci4={:f}", k, ch_ref(1, 5, k), ci4); } if(ido == 1) return; @@ -1526,10 +1534,10 @@ PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform) } -void pffft_destroy_setup(gsl::owner s) noexcept +void PFFFTSetupDeleter::operator()(gsl::owner setup) const noexcept { - std::destroy_at(s); - ::operator delete[](gsl::owner{s}, V4sfAlignVal); + std::destroy_at(setup); + ::operator delete[](gsl::owner{setup}, V4sfAlignVal); } #if !defined(PFFFT_SIMD_DISABLE) @@ -1810,7 +1818,7 @@ NOINLINE void pffft_real_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RE const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ - std::array Xr, Xi; + std::array Xr{}, Xi{}; for(size_t k{0};k < SimdSize;++k) { Xr[k] = vextract0(in[2*k]); @@ -2296,4 +2304,5 @@ void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float pffft_transform_internal(setup, input, output, work, direction, true); } -#endif // defined(PFFFT_SIMD_DISABLE) +#endif /* defined(PFFFT_SIMD_DISABLE) */ +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ diff --git a/libs/openal-soft/common/pffft.h b/libs/openal-soft/common/pffft.h index 31506e9c..4c8fa587 100644 --- a/libs/openal-soft/common/pffft.h +++ b/libs/openal-soft/common/pffft.h @@ -96,9 +96,8 @@ enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD }; /* type of transform */ enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX }; -void pffft_destroy_setup(gsl::owner setup) noexcept; struct PFFFTSetupDeleter { - void operator()(gsl::owner setup) const noexcept { pffft_destroy_setup(setup); } + void operator()(gsl::owner setup) const noexcept; }; using PFFFTSetupPtr = std::unique_ptr; @@ -175,7 +174,7 @@ void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, co struct PFFFTSetup { - PFFFTSetupPtr mSetup{}; + PFFFTSetupPtr mSetup; PFFFTSetup() = default; PFFFTSetup(const PFFFTSetup&) = delete; @@ -189,6 +188,8 @@ struct PFFFTSetup { PFFFTSetup& operator=(const PFFFTSetup&) = delete; PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default; + [[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; } + void transform(const float *input, float *output, float *work, pffft_direction_t direction) const { pffft_transform(mSetup.get(), input, output, work, direction); } @@ -205,8 +206,6 @@ struct PFFFTSetup { void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const { pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); } - - [[nodiscard]] operator bool() const noexcept { return mSetup != nullptr; } }; #endif // PFFFT_H diff --git a/libs/openal-soft/common/phase_shifter.h b/libs/openal-soft/common/phase_shifter.h index a922e8cf..b06e9275 100644 --- a/libs/openal-soft/common/phase_shifter.h +++ b/libs/openal-soft/common/phase_shifter.h @@ -1,95 +1,59 @@ #ifndef PHASE_SHIFTER_H #define PHASE_SHIFTER_H -#ifdef HAVE_SSE_INTRINSICS +#include "config_simd.h" + +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_NEON) +#elif HAVE_NEON #include #endif #include -#include +#include #include -#include -#include -#include "alcomplex.h" +#include "alnumbers.h" #include "alspan.h" +#include "opthelpers.h" -struct NoInit { }; - /* Implements a wide-band +90 degree phase-shift. Note that this should be * given one sample less of a delay (FilterSize/2 - 1) compared to the direct * signal delay (FilterSize/2) to properly align. */ template -struct PhaseShifterT { +struct SIMDALIGN PhaseShifterT { static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); alignas(16) std::array mCoeffs{}; - /* Some notes on this filter construction. - * - * A wide-band phase-shift filter needs a delay to maintain linearity. A - * dirac impulse in the center of a time-domain buffer represents a filter - * passing all frequencies through as-is with a pure delay. Converting that - * to the frequency domain, adjusting the phase of each frequency bin by - * +90 degrees, then converting back to the time domain, results in a FIR - * filter that applies a +90 degree wide-band phase-shift. - * - * A particularly notable aspect of the time-domain filter response is that - * every other coefficient is 0. This allows doubling the effective size of - * the filter, by storing only the non-0 coefficients and double-stepping - * over the input to apply it. - * - * Additionally, the resulting filter is independent of the sample rate. - * The same filter can be applied regardless of the device's sample rate - * and achieve the same effect. - */ PhaseShifterT() { - using complex_d = std::complex; - constexpr std::size_t fft_size{FilterSize}; - constexpr std::size_t half_size{fft_size / 2}; - - auto fftBuffer = std::vector(fft_size, complex_d{}); - fftBuffer[half_size] = 1.0; - - forward_fft(al::span{fftBuffer}); - fftBuffer[0] *= std::numeric_limits::epsilon(); - for(std::size_t i{1};i < half_size;++i) - fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; - fftBuffer[half_size] *= std::numeric_limits::epsilon(); - for(std::size_t i{half_size+1};i < fft_size;++i) - fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); - inverse_fft(al::span{fftBuffer}); - - auto fftiter = fftBuffer.data() + fft_size - 1; - for(float &coeff : mCoeffs) + /* Every other coefficient is 0, so we only need to calculate and store + * the non-0 terms and double-step over the input to apply it. The + * calculated coefficients are in reverse to make applying in the time- + * domain more efficient. + */ + for(std::size_t i{0};i < FilterSize/2;++i) { - coeff = static_cast(fftiter->real() / double{fft_size}); - fftiter -= 2; + const auto k = static_cast(i*2 + 1) - int{FilterSize/2}; + + /* Calculate the Blackman window value for this coefficient. */ + const auto w = 2.0*al::numbers::pi/double{FilterSize} * static_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 = al::numbers::pi * static_cast(k); + mCoeffs[i] = static_cast(window * (1.0-std::cos(pk)) / pk); } } - PhaseShifterT(NoInit) { } - - void process(al::span dst, const float *RESTRICT src) const; + void process(const al::span dst, const al::span src) const; private: -#if defined(HAVE_NEON) - static auto unpacklo(float32x4_t a, float32x4_t b) - { - float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; - return vcombine_f32(result.val[0], result.val[1]); - } - static auto unpackhi(float32x4_t a, float32x4_t b) - { - float32x2x2_t result{vzip_f32(vget_high_f32(a), vget_high_f32(b))}; - return vcombine_f32(result.val[0], result.val[1]); - } +#if HAVE_NEON static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) { float32x4_t ret{vmovq_n_f32(a)}; @@ -98,106 +62,141 @@ private: ret = vsetq_lane_f32(d, ret, 3); return ret; } + static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) + { + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; + } #endif }; template -inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const +NOINLINE inline +void PhaseShifterT::process(const al::span dst, const al::span src) const { -#ifdef HAVE_SSE_INTRINSICS - if(std::size_t todo{dst.size()>>1}) + auto in = src.begin(); +#if HAVE_SSE_INTRINSICS + if(const std::size_t todo{dst.size()>>2}) { - auto *out = reinterpret_cast<__m64*>(dst.data()); - do { - __m128 r04{_mm_setzero_ps()}; - __m128 r14{_mm_setzero_ps()}; + auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + __m128 r0{_mm_setzero_ps()}; + __m128 r1{_mm_setzero_ps()}; + __m128 r2{_mm_setzero_ps()}; + __m128 r3{_mm_setzero_ps()}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 s0{_mm_loadu_ps(&src[j*2])}; - const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + const __m128 s0{_mm_loadu_ps(&in[j*2])}; + const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])}; + const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)}; + const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1), + reinterpret_cast(&in[j*2 + 8]))}; __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; - r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); - r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + 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 += 2; + in += 4; - __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - - _mm_storel_pi(out, r4); - ++out; - } while(--todo); + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - __m128 r4{_mm_setzero_ps()}; - for(std::size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 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)); - } - 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)); - - dst.back() = _mm_cvtss_f32(r4); + __m128 r4{_mm_setzero_ps()}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + ++in; + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + return _mm_cvtss_f32(r4); + }); } -#elif defined(HAVE_NEON) +#elif HAVE_NEON - std::size_t pos{0}; - if(std::size_t todo{dst.size()>>1}) + if(const std::size_t todo{dst.size()>>2}) { - do { - float32x4_t r04{vdupq_n_f32(0.0f)}; - float32x4_t r14{vdupq_n_f32(0.0f)}; + auto out = al::span{reinterpret_cast(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + float32x4_t r0{vdupq_n_f32(0.0f)}; + float32x4_t r1{vdupq_n_f32(0.0f)}; + float32x4_t r2{vdupq_n_f32(0.0f)}; + float32x4_t r3{vdupq_n_f32(0.0f)}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s0{vld1q_f32(&src[j*2])}; - const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; - const float32x4x2_t values{vuzpq_f32(s0, s1)}; + const float32x4_t s0{vld1q_f32(&in[j*2])}; + const float32x4_t s1{vld1q_f32(&in[j*2 + 4])}; + const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))}; + const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))}; + const float32x4x2_t values0{vuzpq_f32(s0, s1)}; + const float32x4x2_t values1{vuzpq_f32(s2, s3)}; - r04 = vmlaq_f32(r04, values.val[0], coeffs); - r14 = vmlaq_f32(r14, values.val[1], coeffs); + 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 += 2; + in += 4; - float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; - float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; - - vst1_f32(&dst[pos], r2); - pos += 2; - } while(--todo); + vtranspose4(r0, r1, r2, r3); + return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - float32x4_t r4{vdupq_n_f32(0.0f)}; - for(std::size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; - r4 = vmlaq_f32(r4, s, coeffs); - } - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + ++in; + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + }); } #else - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&in,this] { float ret{0.0f}; for(std::size_t j{0};j < mCoeffs.size();++j) - ret += src[j*2] * mCoeffs[j]; - - output = ret; - ++src; - } + ret += in[j*2] * mCoeffs[j]; + ++in; + return ret; + }); #endif } diff --git a/libs/openal-soft/common/polyphase_resampler.cpp b/libs/openal-soft/common/polyphase_resampler.cpp index 6aed84ed..d9f727ef 100644 --- a/libs/openal-soft/common/polyphase_resampler.cpp +++ b/libs/openal-soft/common/polyphase_resampler.cpp @@ -17,10 +17,6 @@ namespace { constexpr double Epsilon{1e-9}; -#if __cpp_lib_math_special_functions >= 201603L -using std::cyl_bessel_i; - -#else /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. @@ -33,7 +29,7 @@ using std::cyl_bessel_i; * compounding the rounding and precision error), but it's good enough. */ template -U cyl_bessel_i(T nu, U x) +constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; @@ -57,7 +53,6 @@ U cyl_bessel_i(T nu, U x) } while(sum != last_sum); return static_cast(sum); } -#endif /* This is the normalized cardinal sine (sinc) function. * @@ -89,7 +84,7 @@ double Kaiser(const double beta, const double k, const double besseli_0_beta) { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; + 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 @@ -130,10 +125,10 @@ constexpr double CalcKaiserBeta(const double rejection) * p -- gain compensation factor when sampling * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) */ -double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, - const double cutoff, const uint i) +auto SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, + const double cutoff, const uint i) -> double { - const double x{static_cast(i) - l}; + const auto x = static_cast(i) - l; return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); } @@ -143,77 +138,83 @@ double SincFilter(const uint l, const double beta, const double besseli_0_beta, // that's used to cut frequencies above the destination nyquist. void PPhaseResampler::init(const uint srcRate, const uint dstRate) { - const uint gcd{std::gcd(srcRate, dstRate)}; + const auto gcd = std::gcd(srcRate, dstRate); mP = dstRate / gcd; mQ = srcRate / gcd; - /* The cutoff is adjusted by half the transition width, so the transition - * ends before the nyquist (0.5). Both are scaled by the downsampling - * factor. + /* 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.475 / mP, 0.05 / mP) - : std::make_tuple(0.475 / mQ, 0.05 / mQ); + 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. - const uint l{(CalcKaiserOrder(180.0, width)+1) / 2}; - const double beta{CalcKaiserBeta(180.0)}; - const double besseli_0_beta{cyl_bessel_i(0, beta)}; - mM = l*2 + 1; + static constexpr auto rejection = 180.0; + const auto l = (CalcKaiserOrder(rejection, width)+1u) / 2u; + const auto beta = CalcKaiserBeta(rejection); + const auto besseli_0_beta = ::cyl_bessel_i(0, beta); + mM = l*2u + 1u; mL = l; mF.resize(mM); for(uint i{0};i < mM;i++) - mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i); + mF[i] = 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 al::span in, const al::span out) +void PPhaseResampler::process(const al::span in, const al::span out) const { if(out.empty()) UNLIKELY return; // Handle in-place operation. - std::vector workspace; - al::span work{out}; + auto workspace = std::vector{}; + auto work = al::span{out}; if(work.data() == in.data()) UNLIKELY { workspace.resize(out.size()); work = workspace; } - // Resample the input. - const uint p{mP}, q{mQ}, m{mM}, l{mL}; - const al::span f{mF}; - for(uint i{0};i < out.size();i++) + const auto f = al::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::generate(work.begin(), work.end(), [in,f,p,q,m,&l] { - // Input starts at l to compensate for the filter delay. This will - // drop any build-up from the first half of the filter. - std::size_t j_f{(l + q*i) % p}; - std::size_t j_s{(l + q*i) / p}; + auto j_s = l / p; + auto j_f = l % p; + l += q; // Only take input when 0 <= j_s < in.size(). - double r{0.0}; - if(j_f < m) LIKELY + if(j_f >= m) UNLIKELY + return 0.0; + + auto filt_len = (m - j_f - 1)/p + 1; + if(j_s+1 > in.size()) LIKELY { - std::size_t filt_len{(m-j_f+p-1) / p}; - if(j_s+1 > in.size()) LIKELY - { - std::size_t skip{std::min(j_s+1 - in.size(), filt_len)}; - j_f += p*skip; - j_s -= skip; - filt_len -= skip; - } - std::size_t todo{std::min(j_s+1, filt_len)}; - while(todo) - { - r += f[j_f] * in[j_s]; - j_f += p; --j_s; - --todo; - } + const auto skip = std::min(j_s+1-in.size(), filt_len); + j_f += p*skip; + j_s -= skip; + filt_len -= skip; } - work[i] = r; - } + /* 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::copy(work.cbegin(), work.cend(), out.begin()); diff --git a/libs/openal-soft/common/polyphase_resampler.h b/libs/openal-soft/common/polyphase_resampler.h index 0795c80d..53fb6f81 100644 --- a/libs/openal-soft/common/polyphase_resampler.h +++ b/libs/openal-soft/common/polyphase_resampler.h @@ -37,7 +37,7 @@ using uint = unsigned int; struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); - void process(const al::span in, const al::span out); + void process(const al::span in, const al::span out) const; explicit operator bool() const noexcept { return !mF.empty(); } diff --git a/libs/openal-soft/common/ringbuffer.cpp b/libs/openal-soft/common/ringbuffer.cpp index f4fa49bf..23c48806 100644 --- a/libs/openal-soft/common/ringbuffer.cpp +++ b/libs/openal-soft/common/ringbuffer.cpp @@ -23,12 +23,13 @@ #include "ringbuffer.h" #include +#include #include #include #include -#include #include "alnumeric.h" +#include "alspan.h" auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr @@ -76,8 +77,8 @@ auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; - const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) - : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} + : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, @@ -99,8 +100,8 @@ auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; - const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) - : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} + : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, @@ -121,8 +122,8 @@ auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size const std::size_t write_idx{w & mSizeMask}; const std::size_t wrend{write_idx + to_write}; - const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz) - : std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask); + const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::array{to_write, 0_uz} + : std::array{mSizeMask+1 - write_idx, wrend&mSizeMask}; auto srcbytes = al::span{static_cast(src), count*mElemSize}; std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize)); @@ -146,10 +147,10 @@ auto RingBuffer::getReadVector() noexcept -> DataPair /* Two part vector: the rest of the buffer after the current read ptr, * plus some from the start of the buffer. */ - return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, - {mBuffer.data(), rdend&mSizeMask}}; + return DataPair{{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, + {mBuffer.data(), rdend&mSizeMask}}}; } - return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}}; + return DataPair{{{mBuffer.data() + read_idx*mElemSize, readable}, {}}}; } auto RingBuffer::getWriteVector() noexcept -> DataPair @@ -165,8 +166,8 @@ auto RingBuffer::getWriteVector() noexcept -> DataPair /* Two part vector: the rest of the buffer after the current write ptr, * plus some from the start of the buffer. */ - return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, - {mBuffer.data(), wrend&mSizeMask}}; + return DataPair{{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, + {mBuffer.data(), wrend&mSizeMask}}}; } - return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}}; + return DataPair{{{mBuffer.data() + write_idx*mElemSize, writable}, {}}}; } diff --git a/libs/openal-soft/common/ringbuffer.h b/libs/openal-soft/common/ringbuffer.h index 4493474c..59b0a0ac 100644 --- a/libs/openal-soft/common/ringbuffer.h +++ b/libs/openal-soft/common/ringbuffer.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "almalloc.h" @@ -40,7 +39,7 @@ public: std::byte *buf; std::size_t len; }; - using DataPair = std::pair; + using DataPair = std::array; RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, const std::size_t numbytes) diff --git a/libs/openal-soft/common/strutils.cpp b/libs/openal-soft/common/strutils.cpp index 4bcd4119..5b1b6af1 100644 --- a/libs/openal-soft/common/strutils.cpp +++ b/libs/openal-soft/common/strutils.cpp @@ -11,6 +11,7 @@ #include "alstring.h" +/* NOLINTBEGIN(bugprone-suspicious-stringview-data-usage) */ std::string wstr_to_utf8(std::wstring_view wstr) { std::string ret; @@ -40,6 +41,7 @@ std::wstring utf8_to_wstr(std::string_view str) return ret; } +/* NOLINTEND(bugprone-suspicious-stringview-data-usage) */ #endif namespace al { diff --git a/libs/openal-soft/common/strutils.h b/libs/openal-soft/common/strutils.h index 7eee0c1d..3644847c 100644 --- a/libs/openal-soft/common/strutils.h +++ b/libs/openal-soft/common/strutils.h @@ -5,8 +5,8 @@ #include #ifdef _WIN32 +#include #include -#include std::string wstr_to_utf8(std::wstring_view wstr); std::wstring utf8_to_wstr(std::string_view str); diff --git a/libs/openal-soft/common/vecmat.h b/libs/openal-soft/common/vecmat.h index 4bb7910b..7ac6afe3 100644 --- a/libs/openal-soft/common/vecmat.h +++ b/libs/openal-soft/common/vecmat.h @@ -1,33 +1,35 @@ #ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H +#include #include #include #include #include -#include #include "alspan.h" namespace alu { -template -class VectorR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) std::array mVals; +class Vector { + alignas(16) std::array mVals{}; public: - constexpr VectorR() noexcept = default; - constexpr VectorR(const VectorR&) noexcept = default; - constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { } + constexpr Vector() noexcept = default; + constexpr Vector(const Vector&) noexcept = default; + constexpr Vector(Vector&&) noexcept = default; + constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { } - constexpr VectorR& operator=(const VectorR&) noexcept = default; + constexpr auto operator=(const Vector&) noexcept -> Vector& = default; + constexpr auto operator=(Vector&&) noexcept -> Vector& = default; - constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; } - constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; } - constexpr VectorR& operator+=(const VectorR &rhs) noexcept + constexpr auto operator+=(const Vector &rhs) noexcept -> Vector& { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; @@ -36,85 +38,84 @@ public: return *this; } - constexpr VectorR operator-(const VectorR &rhs) const noexcept + [[nodiscard]] constexpr + auto operator-(const Vector &rhs) const noexcept -> Vector { - return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; } - constexpr T normalize(T limit = std::numeric_limits::epsilon()) + constexpr auto normalize() -> float { - limit = std::max(limit, std::numeric_limits::epsilon()); - const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; - if(length_sqr > limit*limit) + const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; + if(length_sqr > std::numeric_limits::epsilon()) { - const T length{std::sqrt(length_sqr)}; - T inv_length{T{1}/length}; + const auto length = std::sqrt(length_sqr); + auto inv_length = float{1.0f / length}; mVals[0] *= inv_length; mVals[1] *= inv_length; mVals[2] *= inv_length; return length; } - mVals[0] = mVals[1] = mVals[2] = T{0}; - return T{0}; + mVals[0] = mVals[1] = mVals[2] = 0.0f; + return 0.0f; } - [[nodiscard]] constexpr auto cross_product(const alu::VectorR &rhs) const noexcept -> VectorR + [[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector { - return VectorR{ + 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], - T{0}}; + 0.0f}; } - [[nodiscard]] constexpr auto dot_product(const alu::VectorR &rhs) const noexcept -> T + [[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; -using Vector = VectorR; -template -class MatrixR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) std::array mVals; +class Matrix { + alignas(16) std::array mVals{}; public: - constexpr MatrixR() noexcept = default; - constexpr MatrixR(const MatrixR&) noexcept = default; - constexpr explicit MatrixR( - T aa, T ab, T ac, T ad, - T ba, T bb, T bc, T bd, - T ca, T cb, T cc, T cd, - T da, T db, T dc, T dd) noexcept - : mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd} + constexpr Matrix() noexcept = default; + constexpr Matrix(const Matrix&) noexcept = default; + constexpr Matrix(Matrix&&) noexcept = default; + constexpr explicit Matrix( + float aa, float ab, float ac, float ad, + float ba, float bb, float bc, float bd, + float ca, float cb, float cc, float cd, + float da, float db, float dc, float dd) noexcept + : mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}} { } - constexpr MatrixR& operator=(const MatrixR&) noexcept = default; + constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default; + constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default; - constexpr auto operator[](size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } - constexpr auto operator[](size_t idx) const noexcept - { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept + { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept + { return al::span{&mVals[idx*4], 4}; } - static constexpr MatrixR Identity() noexcept + [[nodiscard]] static constexpr auto Identity() noexcept -> Matrix { - return MatrixR{ - T{1}, T{0}, T{0}, T{0}, - T{0}, T{1}, T{0}, T{0}, - T{0}, T{0}, T{1}, T{0}, - T{0}, T{0}, T{0}, T{1}}; + 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]}; } }; -using Matrix = MatrixR; - -template -constexpr VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexcept -{ - return VectorR{ - vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], - vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], - vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], - vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; -} } // namespace alu diff --git a/libs/openal-soft/config.h.in b/libs/openal-soft/config.h.in index 19cb79e0..fb61fcf2 100644 --- a/libs/openal-soft/config.h.in +++ b/libs/openal-soft/config.h.in @@ -1,87 +1,18 @@ /* Define the alignment attribute for externally callable functions. */ #define FORCE_ALIGN @ALSOFT_FORCE_ALIGN@ -/* Define if deprecated EAX extensions are enabled */ -#cmakedefine ALSOFT_EAX - /* 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 the getopt function */ -#cmakedefine HAVE_GETOPT - -/* Define if we have DBus/RTKit */ -#cmakedefine HAVE_RTKIT - -/* Define if we have SSE CPU extensions */ -#cmakedefine HAVE_SSE -#cmakedefine HAVE_SSE2 -#cmakedefine HAVE_SSE3 -#cmakedefine HAVE_SSE4_1 - -/* Define if we have ARM Neon CPU extensions */ -#cmakedefine HAVE_NEON - -/* Define if we have the ALSA backend */ -#cmakedefine HAVE_ALSA - -/* Define if we have the OSS backend */ -#cmakedefine HAVE_OSS - -/* Define if we have the PipeWire backend */ -#cmakedefine HAVE_PIPEWIRE - -/* Define if we have the Solaris backend */ -#cmakedefine HAVE_SOLARIS - -/* Define if we have the SndIO backend */ -#cmakedefine HAVE_SNDIO - -/* Define if we have the WASAPI backend */ -#cmakedefine HAVE_WASAPI - -/* Define if we have the DSound backend */ -#cmakedefine HAVE_DSOUND - -/* Define if we have the Windows Multimedia backend */ -#cmakedefine HAVE_WINMM - -/* Define if we have the PortAudio backend */ -#cmakedefine HAVE_PORTAUDIO - -/* Define if we have the PulseAudio backend */ -#cmakedefine HAVE_PULSEAUDIO - -/* Define if we have the JACK backend */ -#cmakedefine HAVE_JACK - -/* Define if we have the CoreAudio backend */ -#cmakedefine HAVE_COREAUDIO - -/* Define if we have the OpenSL backend */ -#cmakedefine HAVE_OPENSL - -/* Define if we have the Oboe backend */ -#cmakedefine HAVE_OBOE - -/* Define if we have the Wave Writer backend */ -#cmakedefine HAVE_WAVE - -/* Define if we have the SDL2 backend */ -#cmakedefine HAVE_SDL2 - /* 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 malloc.h */ -#cmakedefine HAVE_MALLOC_H - /* Define if we have cpuid.h */ #cmakedefine HAVE_CPUID_H @@ -91,18 +22,12 @@ /* Define if we have guiddef.h */ #cmakedefine HAVE_GUIDDEF_H -/* Define if we have initguid.h */ -#cmakedefine HAVE_INITGUID_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 SSE intrinsics */ -#cmakedefine HAVE_SSE_INTRINSICS - /* Define if we have pthread_setschedparam() */ #cmakedefine HAVE_PTHREAD_SETSCHEDPARAM @@ -115,5 +40,11 @@ /* Define the installation data directory */ #cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" -/* Define whether build alsoft for winuwp */ -#cmakedefine ALSOFT_UWP +/* 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 diff --git a/libs/openal-soft/config_backends.h.in b/libs/openal-soft/config_backends.h.in new file mode 100644 index 00000000..96eb79ca --- /dev/null +++ b/libs/openal-soft/config_backends.h.in @@ -0,0 +1,37 @@ +/* Define to 1 if the given backend is enabled, else 0 */ + +#cmakedefine01 HAVE_ALSA + +#cmakedefine01 HAVE_OSS + +#cmakedefine01 HAVE_PIPEWIRE + +#cmakedefine01 HAVE_SOLARIS + +#cmakedefine01 HAVE_SNDIO + +#cmakedefine01 HAVE_WASAPI + +#cmakedefine01 HAVE_DSOUND + +#cmakedefine01 HAVE_WINMM + +#cmakedefine01 HAVE_PORTAUDIO + +#cmakedefine01 HAVE_PULSEAUDIO + +#cmakedefine01 HAVE_JACK + +#cmakedefine01 HAVE_COREAUDIO + +#cmakedefine01 HAVE_OPENSL + +#cmakedefine01 HAVE_OBOE + +#cmakedefine01 HAVE_OTHERIO + +#cmakedefine01 HAVE_WAVE + +#cmakedefine01 HAVE_SDL3 + +#cmakedefine01 HAVE_SDL2 diff --git a/libs/openal-soft/config_simd.h.in b/libs/openal-soft/config_simd.h.in new file mode 100644 index 00000000..3d284cc7 --- /dev/null +++ b/libs/openal-soft/config_simd.h.in @@ -0,0 +1,10 @@ +/* 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 diff --git a/libs/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini b/libs/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini new file mode 100644 index 00000000..ab44bab5 --- /dev/null +++ b/libs/openal-soft/configs/HRTF+WASAPI Exclusive/alsoft.ini @@ -0,0 +1,8 @@ +[General] +channels=stereo +stereo-mode=headphones +stereo-encoding=hrtf +period_size=1 + +[wasapi] +exclusive-mode=true \ No newline at end of file diff --git a/libs/openal-soft/configs/HRTF/alsoft.ini b/libs/openal-soft/configs/HRTF/alsoft.ini new file mode 100644 index 00000000..a02f0b38 --- /dev/null +++ b/libs/openal-soft/configs/HRTF/alsoft.ini @@ -0,0 +1,4 @@ +[general] +channels=stereo +stereo-mode=headphones +stereo-encoding=hrtf diff --git a/libs/openal-soft/configs/WASAPI Exclusive/alsoft.ini b/libs/openal-soft/configs/WASAPI Exclusive/alsoft.ini new file mode 100644 index 00000000..3a70ee34 --- /dev/null +++ b/libs/openal-soft/configs/WASAPI Exclusive/alsoft.ini @@ -0,0 +1,5 @@ +[General] +period_size=1 + +[wasapi] +exclusive-mode=true \ No newline at end of file diff --git a/libs/openal-soft/core/ambdec.cpp b/libs/openal-soft/core/ambdec.cpp index ba3a4b8b..c1ba4c31 100644 --- a/libs/openal-soft/core/ambdec.cpp +++ b/libs/openal-soft/core/ambdec.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -16,7 +15,8 @@ #include "albit.h" #include "alspan.h" -#include "opthelpers.h" +#include "filesystem.h" +#include "fmt/core.h" namespace { @@ -43,35 +43,13 @@ enum class ReaderScope { HFMatrix, }; -#ifdef __MINGW32__ -[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else -[[gnu::format(printf,2,3)]] -#endif -std::optional make_error(size_t linenum, const char *fmt, ...) +template +auto make_error(size_t linenum, fmt::format_string fmt, Args&& ...args) + -> std::optional { std::optional ret; - auto &str = ret.emplace(); - - str.resize(256); - int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)}; - if(printed < 0) printed = 0; - auto plen = std::min(static_cast(printed), str.length()); - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)}; - if(msglen >= 0 && static_cast(msglen) >= str.size()-plen) - { - str.resize(static_cast(msglen) + plen + 1u); - std::vsnprintf(&str[plen], str.size()-plen, fmt, args2); - } - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - + auto &str = ret.emplace(fmt::format("Line {}: ", linenum)); + str += fmt::format(std::move(fmt), std::forward(args)...); return ret; } @@ -82,7 +60,7 @@ AmbDecConf::~AmbDecConf() = default; std::optional AmbDecConf::load(const char *fname) noexcept { - std::ifstream f{std::filesystem::u8path(fname)}; + fs::ifstream f{fs::u8path(fname)}; if(!f.is_open()) return std::string("Failed to open file \"")+fname+"\""; @@ -105,7 +83,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept if(command == "/}") { if(scope == ReaderScope::Global) - return make_error(linenum, "Unexpected /} in global scope"); + return make_error(linenum, "Unexpected /}} in global scope"); scope = ReaderScope::Global; continue; } @@ -125,7 +103,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept istr >> spkr.Connection; } else - return make_error(linenum, "Unexpected speakers command: %s", command.c_str()); + return make_error(linenum, "Unexpected speakers command: {}", command); } else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) { @@ -168,7 +146,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept } } else - return make_error(linenum, "Unexpected matrix command: %s", command.c_str()); + return make_error(linenum, "Unexpected matrix command: {}", command); } // Global scope commands else if(command == "/description") @@ -185,7 +163,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept return make_error(linenum, "Duplicate version definition"); istr >> Version; if(Version != 3) - return make_error(linenum, "Unsupported version: %d", Version); + return make_error(linenum, "Unsupported version: {}", Version); } else if(command == "/dec/chan_mask") { @@ -194,7 +172,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept istr >> std::hex >> ChanMask >> std::dec; if(!ChanMask || ChanMask > Ambi4OrderMask) - return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask); + 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"); } @@ -204,7 +182,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept return make_error(linenum, "Duplicate freq_bands"); istr >> FreqBands; if(FreqBands != 1 && FreqBands != 2) - return make_error(linenum, "Invalid freq_bands: %u", FreqBands); + return make_error(linenum, "Invalid freq_bands: {}", FreqBands); } else if(command == "/dec/speakers") { @@ -213,7 +191,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept size_t numspeakers{}; istr >> numspeakers; if(!numspeakers) - return make_error(linenum, "Invalid speakers: %zu", numspeakers); + return make_error(linenum, "Invalid speakers: {}", numspeakers); Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") @@ -226,7 +204,7 @@ std::optional AmbDecConf::load(const char *fname) noexcept else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else - return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str()); + 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"); @@ -268,8 +246,8 @@ std::optional AmbDecConf::load(const char *fname) noexcept if(FreqBands == 1) { if(command != "/matrix/{") - return make_error(linenum, "Unexpected \"%s\" for a single-band decoder", - command.c_str()); + return make_error(linenum, "Unexpected \"{}\" for a single-band decoder", + command); scope = ReaderScope::HFMatrix; } else @@ -279,15 +257,16 @@ std::optional AmbDecConf::load(const char *fname) noexcept else if(command == "/hfmatrix/{") scope = ReaderScope::HFMatrix; else - return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder", - command.c_str()); + return make_error(linenum, "Unexpected \"{}\" for a dual-band decoder", + command); } } else if(command == "/end") { const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); + 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())) @@ -298,12 +277,13 @@ std::optional AmbDecConf::load(const char *fname) noexcept return std::nullopt; } else - return make_error(linenum, "Unexpected command: %s", command.c_str()); + return make_error(linenum, "Unexpected command: {}", command); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str()); + return make_error(linenum, "Extra junk on line: {}", + std::string_view{buffer}.substr(endpos)); buffer.clear(); } return make_error(linenum, "Unexpected end of file"); diff --git a/libs/openal-soft/core/ambidefs.cpp b/libs/openal-soft/core/ambidefs.cpp index 2389ce6b..4a72e34f 100644 --- a/libs/openal-soft/core/ambidefs.cpp +++ b/libs/openal-soft/core/ambidefs.cpp @@ -10,7 +10,6 @@ namespace { using AmbiChannelFloatArray = std::array; -constexpr auto inv_sqrt2f = static_cast(1.0/al::numbers::sqrt2); constexpr auto inv_sqrt3f = static_cast(1.0/al::numbers::sqrt3); @@ -76,16 +75,20 @@ static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order * content. */ constexpr std::array FirstOrder2DDecoder{ - std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f}, - std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f}, - std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f}, - std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-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}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f}, + std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f}, }; constexpr std::array FirstOrder2DEncoder{ - CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f), - CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f), - CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f), - CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f), + CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f), + CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f), + CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f), + CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f), + CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f), + CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f), }; static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch"); diff --git a/libs/openal-soft/core/ambidefs.h b/libs/openal-soft/core/ambidefs.h index bea1a312..2e7774a9 100644 --- a/libs/openal-soft/core/ambidefs.h +++ b/libs/openal-soft/core/ambidefs.h @@ -2,8 +2,8 @@ #define CORE_AMBIDEFS_H #include -#include -#include +#include +#include #include "alnumbers.h" @@ -14,10 +14,10 @@ using uint = unsigned int; * 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. */ -inline constexpr uint8_t MaxAmbiOrder{3}; -constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept +constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return (order+1) * (order+1); } -inline constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; +inline constexpr auto MaxAmbiChannels = size_t{AmbiChannelsFromOrder(MaxAmbiOrder)}; /* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up * to 4th order, which is the highest order a 32-bit mask value can specify (a @@ -39,20 +39,20 @@ inline constexpr uint AmbiPeriphonicMask{0xfe7ce4}; * representation. This is 2 per each order above zero-order, plus 1 for zero- * order. Or simply, o*2 + 1. */ -constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept +constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return order*2 + 1; } -inline constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbi2DChannels = Ambi2DChannelsFromOrder(MaxAmbiOrder); /* NOTE: These are scale factors as applied to Ambisonics content. Decoder * coefficients should be divided by these values to get proper scalings. */ struct AmbiScale { - static inline constexpr std::array FromN3D{{ + static constexpr auto FromN3D = std::array{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f - }}; - static inline constexpr std::array FromSN3D{{ + }; + static constexpr auto FromSN3D = std::array{ 1.000000000f, /* ACN 0, sqrt(1) */ 1.732050808f, /* ACN 1, sqrt(3) */ 1.732050808f, /* ACN 2, sqrt(3) */ @@ -69,8 +69,8 @@ struct AmbiScale { 2.645751311f, /* ACN 13, sqrt(7) */ 2.645751311f, /* ACN 14, sqrt(7) */ 2.645751311f, /* ACN 15, sqrt(7) */ - }}; - static inline constexpr std::array FromFuMa{{ + }; + static constexpr auto FromFuMa = std::array{ 1.414213562f, /* ACN 0 (W), sqrt(2) */ 1.732050808f, /* ACN 1 (Y), sqrt(3) */ 1.732050808f, /* ACN 2 (Z), sqrt(3) */ @@ -87,15 +87,15 @@ struct AmbiScale { 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ - }}; - static inline constexpr std::array FromUHJ{{ + }; + static constexpr auto FromUHJ = std::array{ 1.000000000f, /* ACN 0 (W), sqrt(1) */ 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ /* Higher orders not relevant for UHJ. */ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - }}; + }; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ static std::array GetHFOrderScales(const uint src_order, @@ -111,7 +111,7 @@ struct AmbiScale { }; struct AmbiIndex { - static inline constexpr std::array FromFuMa{{ + static constexpr auto FromFuMa = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ @@ -128,8 +128,8 @@ struct AmbiIndex { 10, /* O */ 15, /* P */ 9, /* Q */ - }}; - static inline constexpr std::array FromFuMa2D{{ + }; + static constexpr auto FromFuMa2D = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ @@ -137,23 +137,23 @@ struct AmbiIndex { 4, /* V */ 15, /* P */ 9, /* Q */ - }}; + }; - static inline constexpr std::array FromACN{{ + static constexpr auto FromACN = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - }}; - static inline constexpr std::array FromACN2D{{ + }; + static constexpr auto FromACN2D = std::array{ 0, 1,3, 4,8, 9,15 - }}; + }; - static inline constexpr std::array OrderFromChannel{{ + static constexpr auto OrderFromChannel = std::array{ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }}; - static inline constexpr std::array OrderFrom2DChannel{{ + }; + static constexpr auto OrderFrom2DChannel = std::array{ 0, 1,1, 2,2, 3,3, - }}; + }; }; diff --git a/libs/openal-soft/core/async_event.h b/libs/openal-soft/core/async_event.h index 20857c9c..f865fd8a 100644 --- a/libs/openal-soft/core/async_event.h +++ b/libs/openal-soft/core/async_event.h @@ -2,7 +2,8 @@ #define CORE_EVENT_H #include -#include +#include +#include #include #include "almalloc.h" @@ -12,7 +13,7 @@ struct EffectState; using uint = unsigned int; -enum class AsyncEnableBits : uint8_t { +enum class AsyncEnableBits : std::uint8_t { SourceState, BufferCompleted, Disconnected, @@ -20,7 +21,7 @@ enum class AsyncEnableBits : uint8_t { }; -enum class AsyncSrcState : uint8_t { +enum class AsyncSrcState : std::uint8_t { Reset, Stop, Play, @@ -40,7 +41,7 @@ struct AsyncBufferCompleteEvent { }; struct AsyncDisconnectEvent { - std::array msg; + std::string msg; }; struct AsyncEffectReleaseEvent { diff --git a/libs/openal-soft/core/bformatdec.cpp b/libs/openal-soft/core/bformatdec.cpp index 395ee48e..1e46e950 100644 --- a/libs/openal-soft/core/bformatdec.cpp +++ b/libs/openal-soft/core/bformatdec.cpp @@ -73,12 +73,9 @@ void BFormatDec::process(const al::span OutBuffer, const auto lfSamples = al::span{mSamples[sLFBand]}.first(SamplesToDo); for(auto &chandec : decoder) { - chandec.mXOver.process({input->data(), SamplesToDo}, hfSamples, lfSamples); - MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand].data(), - chandec.mGains[sHFBand].data(), 0, 0); - MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand].data(), - chandec.mGains[sLFBand].data(), 0, 0); - ++input; + chandec.mXOver.process(al::span{*input++}.first(SamplesToDo), hfSamples, lfSamples); + MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand],0,0); + MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand],0,0); } }; auto decode_singleband = [=](std::vector &decoder) @@ -86,9 +83,8 @@ void BFormatDec::process(const al::span OutBuffer, auto input = InSamples.cbegin(); for(auto &chandec : decoder) { - MixSamples(al::span{*input}.first(SamplesToDo), OutBuffer, chandec.mGains.data(), - chandec.mGains.data(), 0, 0); - ++input; + MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains, + chandec.mGains, 0, 0); } }; @@ -106,8 +102,8 @@ void BFormatDec::processStablize(const al::span OutBuffer, */ const auto leftout = al::span{OutBuffer[lidx]}.first(SamplesToDo); const auto rightout = al::span{OutBuffer[ridx]}.first(SamplesToDo); - const al::span mid{al::assume_aligned<16>(mStablizer->MidDirect.data()), SamplesToDo}; - const al::span side{al::assume_aligned<16>(mStablizer->Side.data()), SamplesToDo}; + const auto mid = al::span{mStablizer->MidDirect}.first(SamplesToDo); + const auto side = al::span{mStablizer->Side}.first(SamplesToDo); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{}); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{}); std::fill_n(leftout.begin(), leftout.size(), 0.0f); diff --git a/libs/openal-soft/core/bformatdec.h b/libs/openal-soft/core/bformatdec.h index 5bb2d3b4..b86f771d 100644 --- a/libs/openal-soft/core/bformatdec.h +++ b/libs/openal-soft/core/bformatdec.h @@ -13,11 +13,12 @@ #include "devformat.h" #include "filters/splitter.h" #include "front_stablizer.h" +#include "opthelpers.h" using ChannelDec = std::array; -class BFormatDec { +class SIMDALIGN BFormatDec { static constexpr size_t sHFBand{0}; static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; diff --git a/libs/openal-soft/core/bs2b.cpp b/libs/openal-soft/core/bs2b.cpp index aaac25f4..68b0d575 100644 --- a/libs/openal-soft/core/bs2b.cpp +++ b/libs/openal-soft/core/bs2b.cpp @@ -126,34 +126,36 @@ void bs2b::clear() history.fill(bs2b::t_last_sample{}); } -void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) +void bs2b::cross_feed(const al::span Left, const al::span Right) { - const float a0lo{a0_lo}; - const float b1lo{b1_lo}; - const float a0hi{a0_hi}; - const float a1hi{a1_hi}; - const float b1hi{b1_hi}; - std::array,128> samples; - al::span lsamples{Left, SamplesToDo}; - al::span rsamples{Right, SamplesToDo}; + 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>{}; - while(!lsamples.empty()) + auto leftio = lsamples.begin(); + auto rightio = rsamples.begin(); + while(auto rem = std::distance(leftio, lsamples.end())) { - const size_t todo{std::min(samples.size(), lsamples.size())}; + const auto todo = std::min(samples.size(), rem); /* Process left input */ - float z_lo{history[0].lo}; - float z_hi{history[0].hi}; - std::transform(lsamples.cbegin(), lsamples.cbegin()+ptrdiff_t(todo), samples.begin(), - [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) -> std::array + auto z_lo = history[0].lo; + auto z_hi = history[0].hi; + std::transform(leftio, leftio+todo, samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) noexcept { - float y0{a0hi*x + z_hi}; + const auto y0 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y0; - float y1{a0lo*x + z_lo}; + const auto y1 = a0lo*x + z_lo; z_lo = b1lo*y1; - return {y0, y1}; + return std::array{y0, y1}; }); history[0].lo = z_lo; history[0].hi = z_hi; @@ -161,28 +163,24 @@ void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) /* Process right input */ z_lo = history[1].lo; z_hi = history[1].hi; - std::transform(rsamples.cbegin(), rsamples.cbegin()+ptrdiff_t(todo), samples.begin(), - samples.begin(), - [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array out) -> std::array + std::transform(rightio, rightio+todo, samples.cbegin(), samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array &out) noexcept { - float y0{a0lo*x + z_lo}; + const auto y0 = a0lo*x + z_lo; z_lo = b1lo*y0; - float y1{a0hi*x + z_hi}; + const auto y1 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y1; - return {out[0]+y0, out[1]+y1}; + return std::array{out[0]+y0, out[1]+y1}; }); history[1].lo = z_lo; history[1].hi = z_hi; - auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(), + leftio = std::transform(samples.cbegin(), samples.cbegin()+todo, leftio, [](const std::array &in) { return in[0]; }); - lsamples = {iter, lsamples.end()}; - - iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(), + rightio = std::transform(samples.cbegin(), samples.cbegin()+todo, rightio, [](const std::array &in) { return in[1]; }); - rsamples = {iter, rsamples.end()}; } } diff --git a/libs/openal-soft/core/bs2b.h b/libs/openal-soft/core/bs2b.h index 6fb54c0c..dd6d3ce2 100644 --- a/libs/openal-soft/core/bs2b.h +++ b/libs/openal-soft/core/bs2b.h @@ -25,6 +25,9 @@ #define CORE_BS2B_H #include +#include + +#include "alspan.h" namespace Bs2b { @@ -80,7 +83,7 @@ struct bs2b { /* Clear buffer */ void clear(); - void cross_feed(float *Left, float *Right, size_t SamplesToDo); + void cross_feed(const al::span Left, const al::span Right); }; } // namespace Bs2b diff --git a/libs/openal-soft/core/bsinc_tables.cpp b/libs/openal-soft/core/bsinc_tables.cpp index d321a983..35b197a8 100644 --- a/libs/openal-soft/core/bsinc_tables.cpp +++ b/libs/openal-soft/core/bsinc_tables.cpp @@ -7,12 +7,14 @@ #include #include #include -#include #include +#include #include "alnumbers.h" #include "alnumeric.h" +#include "alspan.h" #include "bsinc_defs.h" +#include "opthelpers.h" #include "resampler_limits.h" @@ -20,10 +22,6 @@ namespace { using uint = unsigned int; -#if __cpp_lib_math_special_functions >= 201603L -using std::cyl_bessel_i; - -#else /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. @@ -36,7 +34,7 @@ using std::cyl_bessel_i; * compounding the rounding and precision error), but it's good enough. */ template -U cyl_bessel_i(T nu, U x) +constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; @@ -60,7 +58,6 @@ U cyl_bessel_i(T nu, U x) } while(sum != last_sum); return static_cast(sum); } -#endif /* This is the normalized cardinal sine (sinc) function. * @@ -93,7 +90,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_ { 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; + 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. @@ -119,74 +116,139 @@ constexpr double CalcKaiserBeta(const double rejection) struct BSincHeader { - double width{}; double beta{}; double scaleBase{}; + double scaleLimit{}; - std::array a{}; + std::array a{}; + std::array m{}; uint total_size{}; - constexpr BSincHeader(uint Rejection, uint Order) noexcept - : width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)} - , scaleBase{width / 2.0} + constexpr BSincHeader(uint rejection, uint order, uint maxScale) noexcept + : beta{CalcKaiserBeta(rejection)}, scaleBase{CalcKaiserWidth(rejection, order) / 2.0} + , scaleLimit{1.0 / maxScale} { - uint num_points{Order+1}; + const auto base_a = (order+1.0) / 2.0; for(uint si{0};si < BSincScaleCount;++si) { - const double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; - const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; - const uint m{2 * a_}; + const auto scale = lerpd(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_ = static_cast(a[si]); + a_ += (static_cast(a_) != a[si]); + m[si] = a_ * 2u; - a[si] = a_; - total_size += 4 * BSincPhaseCount * ((m+3) & ~3u); + 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 the order when downsampling, to 23rd - * and 47th order respectively. + * at nyquist. Each filter will scale up to double size when downsampling, to + * 23rd and 47th order respectively. */ -constexpr BSincHeader bsinc12_hdr{60, 11}; -constexpr BSincHeader bsinc24_hdr{60, 23}; +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 { +struct SIMDALIGN BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() { - static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u}; + static constexpr auto BSincPointsMax = (hdr.m[0]+3u) & ~3u; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); using filter_type = std::array,BSincPhaseCount>; - auto filterptr = std::make_unique>(); - const auto filter = filterptr->begin(); + auto filter = std::vector(BSincScaleCount); - const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)}; + 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(uint si{0};si < BSincScaleCount;++si) { - const uint m{hdr.a[si] * 2}; - const size_t o{(BSincPointsMax-m) / 2}; - const double scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; - const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; - const auto a = static_cast(hdr.a[si]); - const double l{a - 1.0/BSincPhaseCount}; + 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 = lerpd(hdr.scaleBase, 1.0, (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(uint pi{0};pi < BSincPhaseCount;++pi) { - const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; + const auto phase = l + (pi/double{BSincPhaseCount}); for(uint i{0};i < m;++i) { - const double x{i - phase}; - filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff * - Sinc(cutoff*x); + const auto x = static_cast(i) - phase; + filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, besseli_0_beta) * cutoff2 * + Sinc(cutoff2*x); } } } @@ -194,8 +256,8 @@ struct BSincFilterArray { size_t idx{0}; for(size_t si{0};si < BSincScaleCount;++si) { - const size_t m{((hdr.a[si]*2) + 3) & ~3u}; - const size_t o{(BSincPointsMax-m) / 2}; + 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. @@ -279,11 +341,12 @@ struct BSincFilterArray { } [[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; } - [[nodiscard]] constexpr auto getTable() const noexcept -> const float* { return mTable.data(); } + [[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; } }; -const BSincFilterArray bsinc12_filter{}; -const BSincFilterArray bsinc24_filter{}; +const auto bsinc12_filter = BSincFilterArray{}; +const auto bsinc24_filter = BSincFilterArray{}; +const auto bsinc48_filter = BSincFilterArray{}; template constexpr BSincTable GenerateBSincTable(const T &filter) @@ -293,7 +356,7 @@ constexpr BSincTable GenerateBSincTable(const T &filter) ret.scaleBase = static_cast(hdr.scaleBase); ret.scaleRange = static_cast(1.0 / (1.0 - hdr.scaleBase)); for(size_t i{0};i < BSincScaleCount;++i) - ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; + ret.m[i] = (hdr.m[i]+3u) & ~3u; ret.filterOffset[0] = 0; for(size_t i{1};i < BSincScaleCount;++i) ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount; @@ -305,3 +368,4 @@ constexpr BSincTable GenerateBSincTable(const T &filter) const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)}; const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)}; +const BSincTable gBSinc48{GenerateBSincTable(bsinc48_filter)}; diff --git a/libs/openal-soft/core/bsinc_tables.h b/libs/openal-soft/core/bsinc_tables.h index 6c33bd56..516e1ed0 100644 --- a/libs/openal-soft/core/bsinc_tables.h +++ b/libs/openal-soft/core/bsinc_tables.h @@ -3,17 +3,19 @@ #include +#include "alspan.h" #include "bsinc_defs.h" - +#include "opthelpers.h" struct BSincTable { float scaleBase, scaleRange; std::array m; std::array filterOffset; - const float *Tab; + al::span Tab; }; -extern const BSincTable gBSinc12; -extern const BSincTable gBSinc24; +DECL_HIDDEN extern const BSincTable gBSinc12; +DECL_HIDDEN extern const BSincTable gBSinc24; +DECL_HIDDEN extern const BSincTable gBSinc48; #endif /* CORE_BSINC_TABLES_H */ diff --git a/libs/openal-soft/core/buffer_storage.h b/libs/openal-soft/core/buffer_storage.h index ab097cf1..3ffc1f0b 100644 --- a/libs/openal-soft/core/buffer_storage.h +++ b/libs/openal-soft/core/buffer_storage.h @@ -1,10 +1,8 @@ #ifndef CORE_BUFFER_STORAGE_H #define CORE_BUFFER_STORAGE_H -#include #include -#include "alnumeric.h" #include "alspan.h" #include "ambidefs.h" #include "storage_formats.h" @@ -34,7 +32,7 @@ constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept } -using CallbackType = int(*)(void*, void*, int); +using CallbackType = int(*)(void*, void*, int) noexcept; struct BufferStorage { CallbackType mCallback{nullptr}; diff --git a/libs/openal-soft/core/context.cpp b/libs/openal-soft/core/context.cpp index 1e19e8fa..68f6dc38 100644 --- a/libs/openal-soft/core/context.cpp +++ b/libs/openal-soft/core/context.cpp @@ -27,29 +27,22 @@ ContextBase::ContextBase(DeviceBase *device) : mDevice{device} ContextBase::~ContextBase() { - if(auto curarray = mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)) - std::destroy_n(curarray->end(), curarray->size()); - + mActiveAuxSlots.store(nullptr, std::memory_order_relaxed); mVoices.store(nullptr, std::memory_order_relaxed); if(mAsyncEvents) { size_t count{0}; - auto evt_vec = mAsyncEvents->getReadVector(); - if(evt_vec.first.len > 0) + for(auto &evt : mAsyncEvents->getReadVector()) { - std::destroy_n(std::launder(reinterpret_cast(evt_vec.first.buf)), - evt_vec.first.len); - count += evt_vec.first.len; - } - if(evt_vec.second.len > 0) - { - std::destroy_n(std::launder(reinterpret_cast(evt_vec.second.buf)), - evt_vec.second.len); - count += evt_vec.second.len; + if(evt.len > 0) + { + std::destroy_n(std::launder(reinterpret_cast(evt.buf)), evt.len); + count += evt.len; + } } if(count > 0) - TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s"); + TRACE("Destructed {} orphaned event{}", count, (count==1)?"":"s"); mAsyncEvents->readAdvance(count); } } @@ -74,7 +67,7 @@ void ContextBase::allocVoiceProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated voice properties to %zu\n", + TRACE("Increasing allocated voice properties to {}", (mVoicePropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); @@ -106,7 +99,7 @@ void ContextBase::allocVoices(size_t 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 %zu\n", totalcount); + TRACE("Increasing allocated voices to {}", totalcount); while(addcount) { @@ -129,7 +122,7 @@ void ContextBase::allocEffectSlotProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated effect slot properties to %zu\n", + TRACE("Increasing allocated effect slot properties to {}", (mEffectSlotPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); @@ -159,7 +152,7 @@ EffectSlot *ContextBase::getEffectSlot() 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 %zu\n", totalcount); + TRACE("Increasing allocated effect slots to {}", totalcount); mEffectSlotClusters.emplace_back(std::move(clusterptr)); return mEffectSlotClusters.back()->data(); @@ -170,7 +163,7 @@ void ContextBase::allocContextProps() { static constexpr size_t clustersize{std::tuple_size_v}; - TRACE("Increasing allocated context properties to %zu\n", + TRACE("Increasing allocated context properties to {}", (mContextPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); diff --git a/libs/openal-soft/core/context.h b/libs/openal-soft/core/context.h index 901ed1a1..7d98ee8f 100644 --- a/libs/openal-soft/core/context.h +++ b/libs/openal-soft/core/context.h @@ -1,6 +1,8 @@ #ifndef CORE_CONTEXT_H #define CORE_CONTEXT_H +#include "config.h" + #include #include #include @@ -9,7 +11,6 @@ #include #include -#include "almalloc.h" #include "alsem.h" #include "alspan.h" #include "async_event.h" @@ -53,19 +54,22 @@ struct ContextProps { float DopplerFactor; float DopplerVelocity; float SpeedOfSound; +#if ALSOFT_EAX + float DistanceFactor; +#endif bool SourceDistanceModel; DistanceModel mDistanceModel; - std::atomic next; + std::atomic next{}; }; struct ContextParams { /* Pointer to the most recent property values that are awaiting an update. */ std::atomic ContextUpdate{nullptr}; - alu::Vector Position{}; + alu::Vector Position; alu::Matrix Matrix{alu::Matrix::Identity()}; - alu::Vector Velocity{}; + alu::Vector Velocity; float Gain{1.0f}; float MetersPerUnit{1.0f}; @@ -113,7 +117,7 @@ struct ContextBase { ContextParams mParams; using VoiceArray = al::FlexArray; - al::atomic_unique_ptr mVoices{}; + al::atomic_unique_ptr mVoices; std::atomic mActiveVoiceCount{}; void allocVoices(size_t addcount); @@ -130,6 +134,10 @@ struct ContextBase { 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; @@ -168,10 +176,10 @@ struct ContextBase { std::vector mContextPropClusters; - ContextBase(DeviceBase *device); + explicit ContextBase(DeviceBase *device); ContextBase(const ContextBase&) = delete; ContextBase& operator=(const ContextBase&) = delete; - ~ContextBase(); + virtual ~ContextBase(); }; #endif /* CORE_CONTEXT_H */ diff --git a/libs/openal-soft/core/converter.cpp b/libs/openal-soft/core/converter.cpp index 104d1394..97fffc16 100644 --- a/libs/openal-soft/core/converter.cpp +++ b/libs/openal-soft/core/converter.cpp @@ -169,10 +169,11 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::s SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans, uint srcRate, uint dstRate, Resampler resampler) { + SampleConverterPtr converter; if(numchans < 1 || srcRate < 1 || dstRate < 1) - return nullptr; + return converter; - SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}}; + converter = SampleConverterPtr{new(FamCount(numchans)) SampleConverter{numchans}}; converter->mSrcType = srcType; converter->mDstType = dstType; converter->mSrcTypeSize = BytesFromDevFmt(srcType); @@ -189,8 +190,11 @@ SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstTyp MaxPitch*double{MixerFracOne}); converter->mIncrement = std::max(static_cast(step), 1u); if(converter->mIncrement == MixerFracOne) - converter->mResample = [](const InterpState*, const float *RESTRICT src, uint, const uint, - const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }; + { + converter->mResample = [](const InterpState*, const al::span src, uint, + const uint, const al::span dst) + { std::copy_n(src.begin()+MaxResamplerEdge, dst.size(), dst.begin()); }; + } else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); @@ -291,8 +295,7 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ - mResample(&mState, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac, - increment, DstData.first(DstSize)); + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType); } @@ -387,8 +390,7 @@ uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *con std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ - mResample(&mState, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac, - increment, DstData.first(DstSize)); + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); auto DstSamples = al::span{static_cast(dsts[chan]), size_t{mDstTypeSize}*dstframes}.subspan(pos*size_t{mDstTypeSize}); diff --git a/libs/openal-soft/core/converter.h b/libs/openal-soft/core/converter.h index 01649c36..45dc4aa9 100644 --- a/libs/openal-soft/core/converter.h +++ b/libs/openal-soft/core/converter.h @@ -24,7 +24,7 @@ struct SampleConverter { uint mFracOffset{}; uint mIncrement{}; - InterpState mState{}; + InterpState mState; ResamplerFunc mResample{}; alignas(16) FloatBufferLine mSrcSamples{}; @@ -35,7 +35,7 @@ struct SampleConverter { }; al::FlexArray mChan; - SampleConverter(size_t numchans) : mChan{numchans} { } + explicit SampleConverter(size_t numchans) : mChan{numchans} { } [[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint; [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint; diff --git a/libs/openal-soft/core/cpu_caps.cpp b/libs/openal-soft/core/cpu_caps.cpp index 1a064cf4..6f901d24 100644 --- a/libs/openal-soft/core/cpu_caps.cpp +++ b/libs/openal-soft/core/cpu_caps.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include "cpu_caps.h" @@ -23,8 +24,6 @@ #include -int CPUCapFlags{0}; - namespace { #if defined(HAVE_GCC_GET_CPUID) \ @@ -111,22 +110,22 @@ std::optional GetCPUInfo() #else /* Assume support for whatever's supported if we can't check for it */ -#if defined(HAVE_SSE4_1) +#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 defined(HAVE_SSE3) +#elif HAVE_SSE3 #warning "Assuming SSE 3 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; -#elif defined(HAVE_SSE2) +#elif HAVE_SSE2 #warning "Assuming SSE 2 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2; -#elif defined(HAVE_SSE) +#elif HAVE_SSE #warning "Assuming SSE run-time support!" ret.mCaps |= CPU_CAP_SSE; #endif #endif /* CAN_GET_CPUID */ -#ifdef HAVE_NEON +#if HAVE_NEON #ifdef __ARM_NEON ret.mCaps |= CPU_CAP_NEON; #elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) diff --git a/libs/openal-soft/core/cpu_caps.h b/libs/openal-soft/core/cpu_caps.h index 0826a49b..31096e01 100644 --- a/libs/openal-soft/core/cpu_caps.h +++ b/libs/openal-soft/core/cpu_caps.h @@ -5,7 +5,7 @@ #include -extern int CPUCapFlags; +inline int CPUCapFlags{0}; enum { CPU_CAP_SSE = 1<<0, CPU_CAP_SSE2 = 1<<1, diff --git a/libs/openal-soft/core/cubic_tables.cpp b/libs/openal-soft/core/cubic_tables.cpp index adb7abfb..a7cb1019 100644 --- a/libs/openal-soft/core/cubic_tables.cpp +++ b/libs/openal-soft/core/cubic_tables.cpp @@ -9,16 +9,15 @@ #include "alnumeric.h" #include "cubic_defs.h" -/* These 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. +/* 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 is has full + * 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 phase coefficients to more cleanly transition from the - * end of one sample set to the start of the next. + * adjustment for the coefficients to better preserve phase. */ namespace { @@ -27,9 +26,9 @@ auto GetCoeff(double idx) noexcept -> double { const double k{0.5 + idx}; if(k > 512.0) return 0.0; - const double s{ std::sin(al::numbers::pi*1.280/1024 * k)}; - const double t{(std::cos(al::numbers::pi*2.000/1023 * k) - 1.0) * 0.50}; - const double u{(std::cos(al::numbers::pi*4.000/1023 * k) - 1.0) * 0.08}; + const double s{ std::sin(al::numbers::pi*1.280/1024.0 * k)}; + const double t{(std::cos(al::numbers::pi*2.000/1023.0 * k) - 1.0) * 0.50}; + const double u{(std::cos(al::numbers::pi*4.000/1023.0 * k) - 1.0) * 0.08}; return s * (t + u + 1.0) / k; } @@ -69,13 +68,47 @@ GaussianTable::GaussianTable() mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; } +SplineTable::SplineTable() +{ + static constexpr auto third = 1.0/3.0; + static 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(std::size_t pi{0};pi < CubicPhaseCount;++pi) + { + const auto mu = static_cast(pi) / double{CubicPhaseCount}; + const auto mu2 = mu*mu; + const auto mu3 = mu*mu2; + mTable[pi].mCoeffs[0] = static_cast( -third*mu + 0.5*mu2 - sixth*mu3); + mTable[pi].mCoeffs[1] = static_cast(1.0 - 0.5*mu - mu2 + 0.5*mu3); + mTable[pi].mCoeffs[2] = static_cast( mu + 0.5*mu2 - 0.5*mu3); + mTable[pi].mCoeffs[3] = static_cast( -sixth*mu + sixth*mu3); + } + + for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) + { + mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; + } + + static constexpr auto pi = std::size_t{CubicPhaseCount - 1}; + mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; +} + + CubicFilter::CubicFilter() { static constexpr double IndexScale{512.0 / double{sTableSteps*2}}; /* Only half the coefficients need to be iterated here, since Coeff2 and * Coeff3 are just Coeff1 and Coeff0 in reverse respectively. */ - for(size_t i{0};i < sTableSteps/2;++i) + for(size_t i{0};i < sTableSteps/2 + 1;++i) { const double coeff0{GetCoeff(static_cast(sTableSteps + i)*IndexScale)}; const double coeff1{GetCoeff(static_cast(i)*IndexScale)}; diff --git a/libs/openal-soft/core/cubic_tables.h b/libs/openal-soft/core/cubic_tables.h index 366923b6..b3276445 100644 --- a/libs/openal-soft/core/cubic_tables.h +++ b/libs/openal-soft/core/cubic_tables.h @@ -5,15 +5,19 @@ #include #include "cubic_defs.h" +#include "opthelpers.h" -struct CubicTable { +struct SIMDALIGN CubicTable { std::array mTable{}; }; struct GaussianTable : CubicTable { GaussianTable(); }; inline const GaussianTable gGaussianFilter; +struct SplineTable : CubicTable { SplineTable(); }; +inline const SplineTable gSplineFilter; + struct CubicFilter { static constexpr std::size_t sTableBits{8}; diff --git a/libs/openal-soft/core/dbus_wrap.cpp b/libs/openal-soft/core/dbus_wrap.cpp index 05d9fc06..eccfb2c0 100644 --- a/libs/openal-soft/core/dbus_wrap.cpp +++ b/libs/openal-soft/core/dbus_wrap.cpp @@ -3,7 +3,7 @@ #include "dbus_wrap.h" -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #include #include @@ -18,7 +18,7 @@ void PrepareDBus() dbus_handle = LoadLib(libname); if(!dbus_handle) { - WARN("Failed to load %s\n", libname); + WARN("Failed to load {}", libname); return; } @@ -28,7 +28,7 @@ void PrepareDBus() load_func(p##x, #x); \ if(!p##x) \ { \ - WARN("Failed to load function %s\n", #x); \ + WARN("Failed to load function {}", #x); \ CloseLib(dbus_handle); \ dbus_handle = nullptr; \ return; \ diff --git a/libs/openal-soft/core/dbus_wrap.h b/libs/openal-soft/core/dbus_wrap.h index 65f08942..85a996eb 100644 --- a/libs/openal-soft/core/dbus_wrap.h +++ b/libs/openal-soft/core/dbus_wrap.h @@ -7,7 +7,7 @@ #include "dynload.h" -#ifdef HAVE_DYNLOAD +#if HAVE_DYNLOAD #include @@ -63,16 +63,23 @@ inline auto HasDBus() #else constexpr bool HasDBus() noexcept { return true; } -#endif /* HAVE_DYNLOAD */ +#endif namespace dbus { struct Error { Error() { dbus_error_init(&mError); } + Error(const Error&) = delete; + Error(Error&&) = delete; ~Error() { dbus_error_free(&mError); } + + void operator=(const Error&) = delete; + void operator=(Error&&) = delete; + DBusError* operator->() { return &mError; } DBusError &get() { return mError; } + private: DBusError mError{}; }; diff --git a/libs/openal-soft/core/devformat.cpp b/libs/openal-soft/core/devformat.cpp index acdabc4f..fc4a490a 100644 --- a/libs/openal-soft/core/devformat.cpp +++ b/libs/openal-soft/core/devformat.cpp @@ -3,6 +3,11 @@ #include "devformat.h" +#include + +namespace { +using namespace std::string_view_literals; +} // namespace uint BytesFromDevFmt(DevFmtType type) noexcept { @@ -29,39 +34,41 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept 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; } -const char *DevFmtTypeString(DevFmtType type) noexcept +auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view { switch(type) { - case DevFmtByte: return "Int8"; - case DevFmtUByte: return "UInt8"; - case DevFmtShort: return "Int16"; - case DevFmtUShort: return "UInt16"; - case DevFmtInt: return "Int32"; - case DevFmtUInt: return "UInt32"; - case DevFmtFloat: return "Float32"; + 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)"; + return "(unknown type)"sv; } -const char *DevFmtChannelsString(DevFmtChannels chans) noexcept +auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view { switch(chans) { - case DevFmtMono: return "Mono"; - case DevFmtStereo: return "Stereo"; - case DevFmtQuad: return "Quadraphonic"; - case DevFmtX51: return "5.1 Surround"; - case DevFmtX61: return "6.1 Surround"; - case DevFmtX71: return "7.1 Surround"; - case DevFmtX714: return "7.1.4 Surround"; - case DevFmtX3D71: return "3D7.1 Surround"; - case DevFmtAmbi3D: return "Ambisonic 3D"; + 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)"; + return "(unknown channels)"sv; } diff --git a/libs/openal-soft/core/devformat.h b/libs/openal-soft/core/devformat.h index d918e531..beff40de 100644 --- a/libs/openal-soft/core/devformat.h +++ b/libs/openal-soft/core/devformat.h @@ -3,6 +3,7 @@ #include #include +#include using uint = unsigned int; @@ -26,6 +27,11 @@ enum Channel : unsigned char { TopBackCenter, TopBackRight, + BottomFrontLeft, + BottomFrontRight, + BottomBackLeft, + BottomBackRight, + Aux0, Aux1, Aux2, @@ -67,12 +73,13 @@ enum DevFmtChannels : unsigned char { DevFmtX61, DevFmtX71, DevFmtX714, + DevFmtX7144, DevFmtX3D71, DevFmtAmbi3D, DevFmtChannelsDefault = DevFmtStereo }; -inline constexpr size_t MaxOutputChannels{16}; +inline constexpr std::size_t MaxOutputChannels{16}; /* DevFmtType traits, providing the type, etc given a DevFmtType. */ template @@ -102,8 +109,8 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept; inline uint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, uint ambiorder) noexcept { return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } -const char *DevFmtTypeString(DevFmtType type) noexcept; -const char *DevFmtChannelsString(DevFmtChannels chans) noexcept; +auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view; +auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view; enum class DevAmbiLayout : bool { FuMa, diff --git a/libs/openal-soft/core/device.cpp b/libs/openal-soft/core/device.cpp index 795a9601..4c2dc2a3 100644 --- a/libs/openal-soft/core/device.cpp +++ b/libs/openal-soft/core/device.cpp @@ -9,9 +9,6 @@ #include "mastering.h" -static_assert(std::atomic::is_always_lock_free); - - DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{al::FlexArray::Create(0)} { diff --git a/libs/openal-soft/core/device.h b/libs/openal-soft/core/device.h index 1e8ec507..c01888c8 100644 --- a/libs/openal-soft/core/device.h +++ b/libs/openal-soft/core/device.h @@ -5,9 +5,9 @@ #include #include #include +#include +#include #include -#include -#include #include #include "almalloc.h" @@ -18,6 +18,7 @@ #include "devformat.h" #include "filters/nfc.h" #include "flexarray.h" +#include "fmt/core.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" @@ -37,28 +38,28 @@ struct HrtfStore; using uint = unsigned int; -inline constexpr size_t MinOutputRate{8000}; -inline constexpr size_t MaxOutputRate{192000}; -inline constexpr size_t DefaultOutputRate{48000}; +inline constexpr std::size_t MinOutputRate{8000}; +inline constexpr std::size_t MaxOutputRate{192000}; +inline constexpr std::size_t DefaultOutputRate{48000}; -inline constexpr size_t DefaultUpdateSize{960}; /* 20ms */ -inline constexpr size_t DefaultNumUpdates{3}; +inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */ +inline constexpr std::size_t DefaultNumUpdates{3}; -enum class DeviceType : uint8_t { +enum class DeviceType : std::uint8_t { Playback, Capture, Loopback }; -enum class RenderMode : uint8_t { +enum class RenderMode : std::uint8_t { Normal, Pairwise, Hrtf }; -enum class StereoEncoding : uint8_t { +enum class StereoEncoding : std::uint8_t { Basic, Uhj, Hrtf, @@ -80,23 +81,23 @@ struct DistanceComp { static constexpr uint MaxDelay{1024}; struct ChanData { - al::span Buffer{}; /* Valid size is [0...MaxDelay). */ + al::span Buffer; /* Valid size is [0...MaxDelay). */ float Gain{1.0f}; }; std::array mChannels; al::FlexArray mSamples; - DistanceComp(size_t count) : mSamples{count} { } + explicit DistanceComp(std::size_t count) : mSamples{count} { } - static std::unique_ptr Create(size_t numsamples) + static std::unique_ptr Create(std::size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; -constexpr uint8_t InvalidChannelIndex{static_cast(~0u)}; +constexpr auto InvalidChannelIndex = static_cast(~0u); struct BFChannelConfig { float Scale; @@ -120,18 +121,18 @@ struct MixParams { template void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const { - const size_t numIn{inmix.Buffer.size()}; - const size_t numOut{Buffer.size()}; - for(size_t i{0};i < numIn;++i) + const std::size_t numIn{inmix.Buffer.size()}; + const std::size_t numOut{Buffer.size()}; + for(std::size_t i{0};i < numIn;++i) { - uint8_t idx{InvalidChannelIndex}; + std::uint8_t idx{InvalidChannelIndex}; float gain{0.0f}; - for(size_t j{0};j < numOut;++j) + for(std::size_t j{0};j < numOut;++j) { if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) { - idx = static_cast(j); + idx = static_cast(j); gain = AmbiMap[j].Scale * gainbase; break; } @@ -143,7 +144,7 @@ struct MixParams { struct RealMixParams { al::span RemixMap; - std::array ChannelIndex{}; + std::array ChannelIndex{}; al::span Buffer; }; @@ -173,19 +174,22 @@ enum { DeviceFlagsCount }; -enum class DeviceState : uint8_t { +enum class DeviceState : std::uint8_t { Unprepared, Configured, Playing }; -struct DeviceBase { +/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ +struct SIMDALIGN DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; - uint Frequency{}; - uint UpdateSize{}; - uint BufferSize{}; + std::string mDeviceName; + + uint mSampleRate{}; + uint mUpdateSize{}; + uint mBufferSize{}; DevFmtChannels FmtChans{}; DevFmtType FmtType{}; @@ -199,10 +203,8 @@ struct DeviceBase { DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default}; DevAmbiScaling mAmbiScale{DevAmbiScaling::Default}; - std::string DeviceName; - // Device flags - std::bitset Flags{}; + std::bitset Flags; DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; @@ -220,16 +222,21 @@ struct DeviceBase { */ NfcFilter mNFCtrlFilter{}; + using seconds32 = std::chrono::duration; + using nanoseconds32 = std::chrono::duration; + std::atomic mSamplesDone{0u}; - std::atomic mClockBase{std::chrono::nanoseconds{}}; + /* 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 size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; - static constexpr size_t MixerChannelsMax{16}; + static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; + static constexpr std::size_t MixerChannelsMax{16}; alignas(16) std::array mSampleData{}; alignas(16) std::array mResampleData{}; @@ -288,11 +295,6 @@ struct DeviceBase { al::atomic_unique_ptr> mContexts; - DeviceBase(DeviceType type); - DeviceBase(const DeviceBase&) = delete; - DeviceBase& operator=(const DeviceBase&) = delete; - ~DeviceBase(); - [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); } @@ -314,9 +316,8 @@ struct DeviceBase { /* Increment the mix count at the start of mixing and writing clock * info (lsb should be 1). */ - auto mixCount = mMixCount.load(std::memory_order_relaxed); - mMixCount.store(++mixCount, std::memory_order_release); - return MixLock{this, ++mixCount}; + const auto oldCount = mMixCount.fetch_add(1u, std::memory_order_acq_rel); + return MixLock{this, oldCount+2}; } /** Waits for the mixer to not be mixing or updating the clock. */ @@ -337,39 +338,47 @@ struct DeviceBase { using std::chrono::seconds; using std::chrono::nanoseconds; - auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency; - return mClockBase.load(std::memory_order_relaxed) + ns; + auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / mSampleRate; + return nanoseconds{mClockBaseNSec.load(std::memory_order_relaxed)} + + mClockBaseSec.load(std::memory_order_relaxed) + ns; } - void ProcessHrtf(const size_t SamplesToDo); - void ProcessAmbiDec(const size_t SamplesToDo); - void ProcessAmbiDecStablized(const size_t SamplesToDo); - void ProcessUhj(const size_t SamplesToDo); - void ProcessBs2b(const size_t SamplesToDo); + void ProcessHrtf(const std::size_t SamplesToDo); + void ProcessAmbiDec(const std::size_t SamplesToDo); + void ProcessAmbiDecStablized(const std::size_t SamplesToDo); + void ProcessUhj(const std::size_t SamplesToDo); + void ProcessBs2b(const std::size_t SamplesToDo); - inline void postProcess(const size_t SamplesToDo) + void postProcess(const std::size_t SamplesToDo) { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); } - void renderSamples(const al::span outBuffers, const uint numSamples); - void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep); + void renderSamples(const al::span outBuffers, const uint numSamples); + void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ -#ifdef __MINGW32__ - [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else - [[gnu::format(printf,2,3)]] -#endif - void handleDisconnect(const char *msg, ...); + void doDisconnect(std::string msg); + + template + void handleDisconnect(fmt::format_string fmt, Args&& ...args) + { doDisconnect(fmt::format(std::move(fmt), std::forward(args)...)); } /** * Returns the index for the given channel name (e.g. FrontCenter), or * InvalidChannelIndex if it doesn't exist. */ - [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> uint8_t + [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t { return RealOut.ChannelIndex[chan]; } private: uint renderSamples(const uint numSamples); + +protected: + explicit DeviceBase(DeviceType type); + ~DeviceBase(); + +public: + DeviceBase(const DeviceBase&) = delete; + DeviceBase& operator=(const DeviceBase&) = delete; }; /* Must be less than 15 characters (16 including terminating null) for diff --git a/libs/openal-soft/core/effects/base.h b/libs/openal-soft/core/effects/base.h index 2e1eaa75..879f8390 100644 --- a/libs/openal-soft/core/effects/base.h +++ b/libs/openal-soft/core/effects/base.h @@ -8,6 +8,7 @@ #include "alspan.h" #include "core/bufferline.h" #include "intrusive_ptr.h" +#include "opthelpers.h" struct BufferStorage; struct ContextBase; @@ -100,15 +101,6 @@ struct ChorusProps { float Delay; }; -struct FlangerProps { - ChorusWaveform Waveform; - int Phase; - float Rate; - float Depth; - float Feedback; - float Delay; -}; - struct CompressorProps { bool OnOff; }; @@ -170,11 +162,9 @@ struct VmorpherProps { VMorpherWaveform Waveform; }; -struct DedicatedDialogProps { - float Gain; -}; - -struct DedicatedLfeProps { +struct DedicatedProps { + enum TargetType : bool { Dialog, Lfe }; + TargetType Target; float Gain; }; @@ -187,7 +177,6 @@ using EffectProps = std::variant; @@ -206,7 +194,7 @@ struct EffectTarget { RealMixParams *RealOut; }; -struct EffectState : public al::intrusive_ref { +struct SIMDALIGN EffectState : public al::intrusive_ref { al::span mOutTarget; @@ -221,8 +209,14 @@ struct EffectState : public al::intrusive_ref { 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; }; diff --git a/libs/openal-soft/core/effectslot.cpp b/libs/openal-soft/core/effectslot.cpp index ed737f49..d07c79a5 100644 --- a/libs/openal-soft/core/effectslot.cpp +++ b/libs/openal-soft/core/effectslot.cpp @@ -11,8 +11,5 @@ std::unique_ptr EffectSlot::CreatePtrArray(size_t count) { - /* Allocate space for twice as many pointers, so the mixer has scratch - * space to store a sorted list during mixing. - */ - return std::unique_ptr{new(FamCount{count*2}) EffectSlotArray(count)}; + return std::unique_ptr{new(FamCount{count}) EffectSlotArray(count)}; } diff --git a/libs/openal-soft/core/effectslot.h b/libs/openal-soft/core/effectslot.h index 65116ce2..4825670a 100644 --- a/libs/openal-soft/core/effectslot.h +++ b/libs/openal-soft/core/effectslot.h @@ -4,7 +4,6 @@ #include #include -#include "almalloc.h" #include "device.h" #include "effects/base.h" #include "flexarray.h" @@ -20,20 +19,18 @@ enum class EffectSlotType : unsigned char { None, Reverb, Chorus, - Distortion, - Echo, - Flanger, - FrequencyShifter, - VocalMorpher, - PitchShifter, - RingModulator, Autowah, Compressor, + Convolution, + Dedicated, + Distortion, + Echo, Equalizer, - EAXReverb, - DedicatedLFE, - DedicatedDialog, - Convolution + Flanger, + FrequencyShifter, + PitchShifter, + RingModulator, + VocalMorpher, }; struct EffectSlotProps { @@ -46,7 +43,7 @@ struct EffectSlotProps { al::intrusive_ptr State; - std::atomic next; + std::atomic next{}; }; @@ -67,7 +64,7 @@ struct EffectSlot { EffectSlot *Target{nullptr}; EffectSlotType EffectType{EffectSlotType::None}; - EffectProps mEffectProps{}; + EffectProps mEffectProps; al::intrusive_ptr mEffectState; float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ diff --git a/libs/openal-soft/core/except.cpp b/libs/openal-soft/core/except.cpp index f338e9ae..50c085e5 100644 --- a/libs/openal-soft/core/except.cpp +++ b/libs/openal-soft/core/except.cpp @@ -3,30 +3,9 @@ #include "except.h" -#include -#include - -#include "opthelpers.h" - namespace al { base_exception::~base_exception() = default; -void base_exception::setMessage(const char *msg, std::va_list args) -{ - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args2; - va_copy(args2, args); - int msglen{std::vsnprintf(nullptr, 0, msg, args)}; - if(msglen > 0) LIKELY - { - mMessage.resize(static_cast(msglen)+1); - std::vsnprintf(mMessage.data(), mMessage.length(), msg, args2); - mMessage.pop_back(); - } - va_end(args2); - /* NOLINTEND(*-array-to-pointer-decay) */ -} - } // namespace al diff --git a/libs/openal-soft/core/except.h b/libs/openal-soft/core/except.h index 5013893a..53e6a1c9 100644 --- a/libs/openal-soft/core/except.h +++ b/libs/openal-soft/core/except.h @@ -1,10 +1,9 @@ #ifndef CORE_EXCEPT_H #define CORE_EXCEPT_H -#include #include #include -#include +#include namespace al { @@ -12,20 +11,20 @@ namespace al { class base_exception : public std::exception { std::string mMessage; -protected: - auto setMessage(const char *msg, std::va_list args) -> void; - public: base_exception() = default; + template,bool> = true> + explicit base_exception(T&& msg) : mMessage{std::forward(msg)} { } + base_exception(const base_exception&) = default; + base_exception(base_exception&&) = default; ~base_exception() override; + auto operator=(const base_exception&) -> base_exception& = default; + auto operator=(base_exception&&) -> base_exception& = default; + [[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); } }; } // namespace al -#define START_API_FUNC try - -#define END_API_FUNC catch(...) { std::terminate(); } - #endif /* CORE_EXCEPT_H */ diff --git a/libs/openal-soft/core/filters/biquad.h b/libs/openal-soft/core/filters/biquad.h index 19b10ebb..9ced0d00 100644 --- a/libs/openal-soft/core/filters/biquad.h +++ b/libs/openal-soft/core/filters/biquad.h @@ -120,7 +120,7 @@ public: const al::span dst); /* Rather hacky. It's just here to support "manual" processing. */ - [[nodiscard]] auto getComponents() const noexcept -> std::pair { return {mZ1, mZ2}; } + [[nodiscard]] auto getComponents() const noexcept -> std::array { return {{mZ1,mZ2}}; } void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } [[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real { diff --git a/libs/openal-soft/core/filters/nfc.cpp b/libs/openal-soft/core/filters/nfc.cpp index c3c313db..fe650989 100644 --- a/libs/openal-soft/core/filters/nfc.cpp +++ b/libs/openal-soft/core/filters/nfc.cpp @@ -5,8 +5,6 @@ #include -#include "opthelpers.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 @@ -48,29 +46,26 @@ namespace { -constexpr std::array B{ - std::array{ 0.0f, 0.0f, 0.0f, 0.0f}, - std::array{ 1.0f, 0.0f, 0.0f, 0.0f}, - std::array{ 3.0f, 3.0f, 0.0f, 0.0f}, - std::array{3.6778f, 6.4595f, 2.3222f, 0.0f}, - std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f} -}; +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}; NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept { - NfcFilter1 nfc{}; + auto nfc = NfcFilter1{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_00{B[1][0] * r}; - float g_0{1.0f + b_00}; + auto r = 0.5f * w1; + auto b_00 = B1[0] * r; + auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / g_0; nfc.a1 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_00 = B[1][0] * r; + b_00 = B1[0] * r; g_0 = 1.0f + b_00; nfc.gain = nfc.base_gain * g_0; @@ -81,9 +76,9 @@ NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_00{B[1][0] * r}; - const float g_0{1.0f + b_00}; + const auto r = 0.5f * w0; + const auto b_00 = B1[0] * r; + const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * g_0; nfc->b1 = 2.0f * b_00 / g_0; @@ -92,13 +87,13 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept { - NfcFilter2 nfc{}; + auto nfc = NfcFilter2{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[2][0] * r}; - float b_11{B[2][1] * r * r}; - float g_1{1.0f + b_10 + b_11}; + auto r = 0.5f * w1; + auto b_10 = B2[0] * r; + auto b_11 = B2[1] * (r*r); + auto g_1 = 1.0f + b_10 + b_11; nfc.base_gain = 1.0f / g_1; nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -106,8 +101,8 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[2][0] * r; - b_11 = B[2][1] * r * r; + b_10 = B2[0] * r; + b_11 = B2[1] * r * r; g_1 = 1.0f + b_10 + b_11; nfc.gain = nfc.base_gain * g_1; @@ -119,10 +114,10 @@ NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[2][0] * r}; - const float b_11{B[2][1] * r * r}; - const float g_1{1.0f + b_10 + b_11}; + 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->gain = nfc->base_gain * g_1; nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -132,15 +127,15 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept { - NfcFilter3 nfc{}; + auto nfc = NfcFilter3{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[3][0] * r}; - float b_11{B[3][1] * r * r}; - float b_00{B[3][2] * r}; - float g_1{1.0f + b_10 + b_11}; - float g_0{1.0f + b_00}; + auto r = 0.5f * w1; + auto b_10 = B3[0] * r; + auto b_11 = B3[1] * (r*r); + auto b_00 = B3[2] * r; + auto g_1 = 1.0f + b_10 + b_11; + auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -149,9 +144,9 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[3][0] * r; - b_11 = B[3][1] * r * r; - b_00 = B[3][2] * r; + b_10 = B3[0] * r; + b_11 = B3[1] * (r*r); + b_00 = B3[2] * r; g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; @@ -165,12 +160,12 @@ NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[3][0] * r}; - const float b_11{B[3][1] * r * r}; - const float b_00{B[3][2] * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00}; + const auto r = 0.5f * w0; + const auto b_10 = B3[0] * r; + const auto b_11 = B3[1] * (r*r); + const auto b_00 = B3[2] * r; + const auto g_1 = 1.0f + b_10 + b_11; + const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -181,16 +176,16 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept { - NfcFilter4 nfc{}; + auto nfc = NfcFilter4{}; /* Calculate bass-cut coefficients. */ - float r{0.5f * w1}; - float b_10{B[4][0] * r}; - float b_11{B[4][1] * r * r}; - float b_00{B[4][2] * r}; - float b_01{B[4][3] * r * r}; - float g_1{1.0f + b_10 + b_11}; - float g_0{1.0f + b_00 + b_01}; + auto r = 0.5f * w1; + auto b_10 = B4[0] * r; + auto b_11 = B4[1] * (r*r); + auto b_00 = B4[2] * r; + auto b_01 = B4[3] * (r*r); + auto g_1 = 1.0f + b_10 + b_11; + auto g_0 = 1.0f + b_00 + b_01; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -200,10 +195,10 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept /* Calculate bass-boost coefficients. */ r = 0.5f * w0; - b_10 = B[4][0] * r; - b_11 = B[4][1] * r * r; - b_00 = B[4][2] * r; - b_01 = B[4][3] * r * r; + b_10 = B4[0] * r; + b_11 = B4[1] * (r*r); + b_00 = B4[2] * r; + b_01 = B4[3] * (r*r); g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; @@ -218,13 +213,13 @@ NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept { - const float r{0.5f * w0}; - const float b_10{B[4][0] * r}; - const float b_11{B[4][1] * r * r}; - const float b_00{B[4][2] * r}; - const float b_01{B[4][3] * r * r}; - const float g_1{1.0f + b_10 + b_11}; - const float g_0{1.0f + b_00 + b_01}; + const auto r = 0.5f * w0; + const auto b_10 = B4[0] * r; + const auto b_11 = B4[1] * (r*r); + const auto b_00 = B4[2] * r; + const auto b_01 = B4[3] * (r*r); + const auto g_1 = 1.0f + b_10 + b_11; + const auto g_0 = 1.0f + b_00 + b_01; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; diff --git a/libs/openal-soft/core/filters/splitter.h b/libs/openal-soft/core/filters/splitter.h index e3658c19..fdbf07f7 100644 --- a/libs/openal-soft/core/filters/splitter.h +++ b/libs/openal-soft/core/filters/splitter.h @@ -17,7 +17,7 @@ class BandSplitterR { public: BandSplitterR() = default; BandSplitterR(const BandSplitterR&) = default; - BandSplitterR(Real f0norm) { init(f0norm); } + explicit BandSplitterR(Real f0norm) { init(f0norm); } BandSplitterR& operator=(const BandSplitterR&) = default; void init(Real f0norm); diff --git a/libs/openal-soft/core/fmt_traits.cpp b/libs/openal-soft/core/fmt_traits.cpp deleted file mode 100644 index 9d79287d..00000000 --- a/libs/openal-soft/core/fmt_traits.cpp +++ /dev/null @@ -1,79 +0,0 @@ - -#include "config.h" - -#include "fmt_traits.h" - - -namespace al { - -const std::array muLawDecompressionTable{{ - -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 -}}; - -const std::array aLawDecompressionTable{{ - -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 -}}; - -} // namespace al diff --git a/libs/openal-soft/core/fmt_traits.h b/libs/openal-soft/core/fmt_traits.h index e87ea57c..b16f4a3d 100644 --- a/libs/openal-soft/core/fmt_traits.h +++ b/libs/openal-soft/core/fmt_traits.h @@ -9,8 +9,75 @@ namespace al { -extern const std::array muLawDecompressionTable; -extern const std::array aLawDecompressionTable; +inline constexpr auto muLawDecompressionTable = std::array{{ + -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, + -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, + -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, + -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}}; + +inline constexpr auto aLawDecompressionTable = std::array{{ + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, + -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, + -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, + -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 +}}; template diff --git a/libs/openal-soft/core/fpu_ctrl.cpp b/libs/openal-soft/core/fpu_ctrl.cpp index 28e60c04..7a4f147f 100644 --- a/libs/openal-soft/core/fpu_ctrl.cpp +++ b/libs/openal-soft/core/fpu_ctrl.cpp @@ -1,24 +1,27 @@ #include "config.h" +#include "config_simd.h" #include "fpu_ctrl.h" #ifdef HAVE_INTRIN_H #include #endif -#ifdef HAVE_SSE_INTRINSICS +#if HAVE_SSE_INTRINSICS #include -#elif defined(HAVE_SSE) +#elif HAVE_SSE #include #endif -#if defined(HAVE_SSE) && !defined(_MM_DENORMALS_ZERO_MASK) +#if HAVE_SSE && !defined(_MM_DENORMALS_ZERO_MASK) /* Some headers seem to be missing these? */ #define _MM_DENORMALS_ZERO_MASK 0x0040u #define _MM_DENORMALS_ZERO_ON 0x0040u #endif +#if !HAVE_SSE_INTRINSICS && HAVE_SSE #include "cpu_caps.h" +#endif namespace { @@ -28,14 +31,14 @@ namespace { [[maybe_unused]] void disable_denormals(unsigned int *state [[maybe_unused]]) { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS *state = _mm_getcsr(); unsigned int sseState{*state}; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); -#elif defined(HAVE_SSE) +#elif HAVE_SSE *state = _mm_getcsr(); unsigned int sseState{*state}; @@ -56,7 +59,7 @@ void disable_denormals(unsigned int *state [[maybe_unused]]) [[maybe_unused]] void reset_fpu(unsigned int state [[maybe_unused]]) { -#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE) +#if HAVE_SSE_INTRINSICS || HAVE_SSE _mm_setcsr(state); #endif } @@ -67,9 +70,9 @@ void reset_fpu(unsigned int state [[maybe_unused]]) unsigned int FPUCtl::Set() noexcept { unsigned int state{}; -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS disable_denormals(&state); -#elif defined(HAVE_SSE) +#elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) disable_denormals(&state); #endif @@ -78,9 +81,9 @@ unsigned int FPUCtl::Set() noexcept void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept { -#if defined(HAVE_SSE_INTRINSICS) +#if HAVE_SSE_INTRINSICS reset_fpu(state); -#elif defined(HAVE_SSE) +#elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) reset_fpu(state); #endif diff --git a/libs/openal-soft/core/front_stablizer.h b/libs/openal-soft/core/front_stablizer.h index 8eeb6d74..62652699 100644 --- a/libs/openal-soft/core/front_stablizer.h +++ b/libs/openal-soft/core/front_stablizer.h @@ -11,7 +11,7 @@ struct FrontStablizer { - FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } + explicit FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } alignas(16) std::array MidDirect{}; alignas(16) std::array Side{}; diff --git a/libs/openal-soft/core/helpers.cpp b/libs/openal-soft/core/helpers.cpp index d9fa8e9f..96fca106 100644 --- a/libs/openal-soft/core/helpers.cpp +++ b/libs/openal-soft/core/helpers.cpp @@ -11,17 +11,18 @@ #include #include #include -#include #include #include #include #include +#include #include #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" #include "logging.h" #include "strutils.h" @@ -32,46 +33,38 @@ using namespace std::string_view_literals; std::mutex gSearchLock; -void DirectorySearch(const std::filesystem::path &path, const std::string_view ext, +void DirectorySearch(const fs::path &path, const std::string_view ext, std::vector *const results) { - namespace fs = std::filesystem; - - const auto base = static_cast>(results->size()); + const auto base = results->size(); try { auto fpath = path.lexically_normal(); if(!fs::exists(fpath)) return; - TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data()); + 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 - && al::case_compare(entrypath.extension().u8string(), ext) == 0) - results->emplace_back(entrypath.u8string()); + 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: %s\n", e.what()); + ERR("Exception enumerating files: {}", e.what()); } - /* HACK: Without the size check this trips up range-checked iterators, as - * al::span uses al::to_address to get the first iterator's data pointer, - * which relies on operator->(), which can assert on end iterators. The - * check shouldn't be needed with C++20's std::span. - */ - if(static_cast(base) < results->size()) - { - const al::span newlist{results->begin()+base, results->end()}; - std::sort(newlist.begin(), newlist.end()); - for(const auto &name : newlist) - TRACE(" got %s\n", name.c_str()); - } + const auto newlist = al::span{*results}.subspan(base); + std::sort(newlist.begin(), newlist.end()); + for(const auto &name : newlist) + TRACE(" got {}", name); } } // namespace @@ -85,7 +78,7 @@ const PathNamePair &GetProcBinary() { auto get_procbin = [] { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP DWORD pathlen{256}; auto fullpath = std::wstring(pathlen, L'\0'); DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)}; @@ -103,16 +96,22 @@ const PathNamePair &GetProcBinary() } if(len == 0) { - ERR("Failed to get process name: error %lu\n", GetLastError()); + 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 WCHAR *exePath{__wargv[0]}; if(!exePath) { - ERR("Failed to get process name: __wargv[0] == nullptr\n"); + ERR("Failed to get process name: __wargv[0] == nullptr"); return PathNamePair{}; } std::wstring fullpath{exePath}; @@ -128,7 +127,7 @@ const PathNamePair &GetProcBinary() else res.fname = wstr_to_utf8(fullpath); - TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str()); + TRACE("Got binary: {}, {}", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; @@ -137,7 +136,7 @@ const PathNamePair &GetProcBinary() namespace { -#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +#if !ALSOFT_UWP && !defined(_GAMING_XBOX) struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; @@ -145,26 +144,35 @@ struct CoTaskMemDeleter { } // namespace -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +auto SearchDataFiles(const std::string_view ext) -> std::vector +{ + auto srchlock = std::lock_guard{gSearchLock}; + + /* Search the app-local directory. */ + auto results = std::vector{}; + if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = fs::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); + + return results; +} + +auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) + -> std::vector { std::lock_guard srchlock{gSearchLock}; /* If the path is absolute, use it directly. */ std::vector results; - auto path = std::filesystem::u8path(subdir); + auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } - /* Search the app-local directory. */ - if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) - DirectorySearch(*localpath, ext, &results); - else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) - DirectorySearch(curpath, ext, &results); - -#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +#if !ALSOFT_UWP && !defined(_GAMING_XBOX) /* Search the local and global data dirs. */ for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData}) { @@ -174,7 +182,7 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: if(FAILED(hr) || !buffer || !*buffer) continue; - DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results); + DirectorySearch(fs::path{buffer.get()}/path, ext, &results); } #endif @@ -183,11 +191,11 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: void SetRTPriority() { -#if !defined(ALSOFT_UWP) +#if !ALSOFT_UWP if(RTPrioLevel > 0) { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) - ERR("Failed to set priority level for thread\n"); + ERR("Failed to set priority level for thread"); } #endif } @@ -210,7 +218,7 @@ void SetRTPriority() #include #include #endif -#ifdef HAVE_RTKIT +#if HAVE_RTKIT #include #include "dbus_wrap.h" @@ -229,8 +237,8 @@ const PathNamePair &GetProcBinary() size_t pathlen{}; std::array mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}}; if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1) - WARN("Failed to sysctl kern.proc.pathname: %s\n", - std::generic_category().message(errno).c_str()); + WARN("Failed to sysctl kern.proc.pathname: {}", + std::generic_category().message(errno)); else { auto procpath = std::vector(pathlen+1, '\0'); @@ -244,8 +252,8 @@ const PathNamePair &GetProcBinary() std::array procpath{}; const pid_t pid{getpid()}; if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1) - ERR("proc_pidpath(%d, ...) failed: %s\n", pid, - std::generic_category().message(errno).c_str()); + ERR("proc_pidpath({}, ...) failed: {}", pid, + std::generic_category().message(errno)); else pathname = procpath.data(); } @@ -271,17 +279,16 @@ const PathNamePair &GetProcBinary() for(const std::string_view name : SelfLinkNames) { try { - if(!std::filesystem::exists(name)) + if(!fs::exists(name)) continue; - if(auto path = std::filesystem::read_symlink(name); !path.empty()) + if(auto path = fs::read_symlink(name); !path.empty()) { - pathname = path.u8string(); + pathname = al::u8_as_char(path.u8string()); break; } } catch(std::exception& e) { - WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(), - e.what()); + WARN("Exception getting symlink {}: {}", name, e.what()); } } } @@ -296,36 +303,45 @@ const PathNamePair &GetProcBinary() else res.fname = pathname; - TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str()); + TRACE("Got binary: \"{}\", \"{}\"", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; return procbin; } -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +auto SearchDataFiles(const std::string_view ext) -> std::vector +{ + auto srchlock = std::lock_guard{gSearchLock}; + + /* Search the app-local directory. */ + auto results = std::vector{}; + if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = fs::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); + + return results; +} + +auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) + -> std::vector { std::lock_guard srchlock{gSearchLock}; std::vector results; - auto path = std::filesystem::u8path(subdir); + auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } - /* Search the app-local directory. */ - if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) - DirectorySearch(*localpath, ext, &results); - else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) - DirectorySearch(curpath, ext, &results); - /* Search local data dir */ if(auto datapath = al::getenv("XDG_DATA_HOME")) - DirectorySearch(std::filesystem::path{*datapath}/path, ext, &results); + DirectorySearch(fs::path{*datapath}/path, ext, &results); else if(auto homepath = al::getenv("HOME")) - DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results); + DirectorySearch(fs::path{*homepath}/".local/share"/path, ext, &results); /* Search global data dirs */ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; @@ -341,12 +357,12 @@ std::vector SearchDataFiles(const std::string_view ext, const std:: curpos = nextpos; if(!pathname.empty()) - DirectorySearch(std::filesystem::path{pathname}/path, ext, &results); + DirectorySearch(fs::path{pathname}/path, ext, &results); } #ifdef ALSOFT_INSTALL_DATADIR /* Search the installation data directory */ - if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) + if(auto instpath = fs::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) DirectorySearch(instpath/path, ext, &results); #endif @@ -376,24 +392,23 @@ bool SetRTPriorityPthread(int prio [[maybe_unused]]) err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if(err == 0) return true; #endif - WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(), - err); + WARN("pthread_setschedparam failed: {} ({})", std::generic_category().message(err), err); return false; } bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { -#ifdef HAVE_RTKIT +#if HAVE_RTKIT if(!HasDBus()) { - WARN("D-Bus not available\n"); + WARN("D-Bus not available"); return false; } dbus::Error error; dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())}; if(!conn) { - WARN("D-Bus connection failed with %s: %s\n", error->name, error->message); + WARN("D-Bus connection failed with {}: {}", error->name, error->message); return false; } @@ -405,11 +420,11 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) if(err == -ENOENT) { err = std::abs(err); - ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err); + ERR("Could not query RTKit: {} ({})", std::generic_category().message(err), err); return false; } int rtmax{rtkit_get_max_realtime_priority(conn.get())}; - TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + TRACE("Maximum real-time priority: {}, minimum niceness: {}", rtmax, nicemin); auto limit_rttime = [](DBusConnection *c) -> int { @@ -422,8 +437,7 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; - TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, - static_cast(rlim.rlim_max), static_cast(rlim.rlim_cur)); + TRACE("RTTime max: {} (hard: {}, soft: {})", umaxtime, rlim.rlim_max, rlim.rlim_cur); if(rlim.rlim_max > umaxtime) { rlim.rlim_max = static_cast(std::min(umaxtime, @@ -440,21 +454,21 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { err = limit_rttime(conn.get()); if(err != 0) - WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", - std::generic_category().message(err).c_str(), err); + 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 %d (max: %d)\n", prio, 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: %s (%d)\n", - std::generic_category().message(err).c_str(), 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 @@ -464,19 +478,18 @@ bool SetRTPriorityRTKit(int prio [[maybe_unused]]) #ifdef __linux__ if(nicemin < 0) { - TRACE("Making high priority with niceness %d\n", nicemin); + 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: %s (%d)\n", - std::generic_category().message(err).c_str(), err); + WARN("Failed to set high priority: {} ({})", std::generic_category().message(err), err); } #endif /* __linux__ */ #else - WARN("D-Bus not supported\n"); + WARN("D-Bus not supported"); #endif return false; } diff --git a/libs/openal-soft/core/helpers.h b/libs/openal-soft/core/helpers.h index 3a987c9d..96987a2e 100644 --- a/libs/openal-soft/core/helpers.h +++ b/libs/openal-soft/core/helpers.h @@ -19,6 +19,8 @@ inline bool AllowRTTimeLimit{true}; void SetRTPriority(); -std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir); +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 */ diff --git a/libs/openal-soft/core/hrtf.cpp b/libs/openal-soft/core/hrtf.cpp index 6bbdf262..ccd0a11a 100644 --- a/libs/openal-soft/core/hrtf.cpp +++ b/libs/openal-soft/core/hrtf.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +30,9 @@ #include "alspan.h" #include "alstring.h" #include "ambidefs.h" +#include "filesystem.h" #include "filters/splitter.h" +#include "fmt/core.h" #include "helpers.h" #include "logging.h" #include "mixer/hrtfdefs.h" @@ -103,11 +104,16 @@ constexpr uint MaxSampleRate{0xff'ff'ff}; static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); +constexpr auto HeaderMarkerSize = 8_uz; [[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; } [[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; } [[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; } [[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; } +static_assert(GetMarker00Name().size() == HeaderMarkerSize); +static_assert(GetMarker01Name().size() == HeaderMarkerSize); +static_assert(GetMarker02Name().size() == HeaderMarkerSize); +static_assert(GetMarker03Name().size() == HeaderMarkerSize); /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ @@ -120,6 +126,11 @@ std::mutex EnumeratedHrtfLock; std::vector EnumeratedHrtfs; +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * To access a memory buffer through the std::istream interface, a custom + * std::streambuf implementation is needed that has to do pointer manipulation + * for seeking. With C++23, we may be able to use std::spanstream instead. + */ class databuf final : public std::streambuf { int_type underflow() override { return traits_type::eof(); } @@ -171,17 +182,18 @@ class databuf final : public std::streambuf { } public: - databuf(const al::span data) noexcept + explicit databuf(const al::span data) noexcept { setg(data.data(), data.data(), al::to_address(data.end())); } }; +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ class idstream final : public std::istream { databuf mStreamBuf; public: - idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} + explicit idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; @@ -192,10 +204,9 @@ struct IdxBlend { uint idx; float blend; }; */ IdxBlend CalcEvIndex(uint evcount, float ev) { - ev = (al::numbers::pi_v*0.5f + ev) * static_cast(evcount-1) * - al::numbers::inv_pi_v; - uint idx{float2uint(ev)}; + ev = (al::numbers::inv_pi_v*ev + 0.5f) * static_cast(evcount-1); + const auto idx = float2uint(ev); return IdxBlend{std::min(idx, evcount-1u), ev-static_cast(idx)}; } @@ -204,10 +215,9 @@ IdxBlend CalcEvIndex(uint evcount, float ev) */ IdxBlend CalcAzIndex(uint azcount, float az) { - az = (al::numbers::pi_v*2.0f + az) * static_cast(azcount) * - (al::numbers::inv_pi_v*0.5f); - uint idx{float2uint(az)}; + az = (al::numbers::inv_pi_v*0.5f*az + 1.0f) * static_cast(azcount); + const auto idx = float2uint(az); return IdxBlend{idx%azcount, az-static_cast(idx)}; } @@ -218,7 +228,7 @@ IdxBlend CalcAzIndex(uint azcount, float az) * and azimuth in radians. The coefficients are normalized. */ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread, - HrirArray &coeffs, const al::span delays) const + const HrirSpan coeffs, const al::span delays) const { const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; @@ -269,17 +279,17 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne}); /* Calculate the blended HRIR coefficients. */ - float *coeffout{al::assume_aligned<16>(coeffs[0].data())}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f); + auto coeffout = coeffs.begin(); + coeffout[0][0] = PassthruCoeff * (1.0f-dirfact); + coeffout[0][1] = PassthruCoeff * (1.0f-dirfact); + std::fill_n(coeffout+1, size_t{HrirLength-1}, std::array{0.0f, 0.0f}); for(size_t c{0};c < 4;c++) { - const float *srccoeffs{al::assume_aligned<16>(mCoeffs[idx[c]][0].data())}; const float mult{blend[c]}; - auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + HrirLength*2_uz, coeffout, coeffout, blend_coeffs); + auto blend_coeffs = [mult](const float2 &src, const float2 &coeff) noexcept -> float2 + { return float2{{src[0]*mult + coeff[0], src[1]*mult + coeff[1]}}; }; + std::transform(mCoeffs[idx[c]].cbegin(), mCoeffs[idx[c]].cend(), coeffout, coeffout, + blend_coeffs); } } @@ -342,7 +352,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool auto hrir_delay_round = [](const uint d) noexcept -> uint { return (d+HrirDelayFracHalf) >> HrirDelayFracBits; }; - TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", + TRACE("Min delay: {:.2f}, max delay: {:.2f}, FIR length: {}", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); auto tmpres = std::vector>(mChannels.size()); @@ -383,7 +393,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool tmpres.clear(); const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)}; - TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, + TRACE("New max delay: {:.2f}, FIR length: {}", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; } @@ -420,35 +430,38 @@ std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, Hrtf->mIrSize = irSize; /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast(Hrtf.get()); - size_t offset{sizeof(HrtfStore)}; + auto storage = al::span{reinterpret_cast(Hrtf.get()), total}; + auto base = storage.begin(); + ptrdiff_t offset{sizeof(HrtfStore)}; offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ - auto field_ = reinterpret_cast(base + offset); - offset += sizeof(field_[0])*fields.size(); + auto field_ = al::span{reinterpret_cast(al::to_address(base + offset)), + fields.size()}; + offset += ptrdiff_t(sizeof(field_[0])*fields.size()); offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ - auto elev_ = reinterpret_cast(base + offset); - offset += sizeof(elev_[0])*elevs.size(); + auto elev_ = al::span{reinterpret_cast(al::to_address(base + offset)), + elevs.size()}; + offset += ptrdiff_t(sizeof(elev_[0])*elevs.size()); offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ - auto coeffs_ = reinterpret_cast(base + offset); - offset += sizeof(coeffs_[0])*irCount; + auto coeffs_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(coeffs_[0])*irCount); - auto delays_ = reinterpret_cast(base + offset); - offset += sizeof(delays_[0])*irCount; + auto delays_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(delays_[0])*irCount); - if(offset != total) + if(size_t(offset) != total) throw std::runtime_error{"HrtfStore allocation size mismatch"}; /* Copy input data to storage. */ - std::uninitialized_copy(fields.cbegin(), fields.cend(), field_); - std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); - std::uninitialized_copy_n(coeffs, irCount, coeffs_); - std::uninitialized_copy_n(delays, irCount, delays_); + std::uninitialized_copy(fields.cbegin(), fields.cend(), field_.begin()); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_.begin()); + std::uninitialized_copy_n(coeffs, irCount, coeffs_.begin()); + std::uninitialized_copy_n(delays, irCount, delays_.begin()); /* Finally, assign the storage pointers. */ - Hrtf->mFields = {field_, fields.size()}; + Hrtf->mFields = field_; Hrtf->mElev = elev_; Hrtf->mCoeffs = coeffs_; Hrtf->mDelays = delays_; @@ -456,8 +469,8 @@ std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, return Hrtf; } -void MirrorLeftHrirs(const al::span elevs, HrirArray *coeffs, - ubyte2 *delays) +void MirrorLeftHrirs(const al::span elevs, al::span coeffs, + al::span delays) { for(const auto &elev : elevs) { @@ -535,13 +548,13 @@ std::unique_ptr LoadHrtf00(std::istream &data) if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MinEvCount, MaxEvCount); + ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, + MaxEvCount); return nullptr; } @@ -555,15 +568,15 @@ std::unique_ptr LoadHrtf00(std::istream &data) { if(elevs[i].irOffset <= elevs[i-1].irOffset) { - ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", i, elevs[i].irOffset, + ERR("Invalid evOffset: evOffset[{}]={} (last={})", i, elevs[i].irOffset, elevs[i-1].irOffset); return nullptr; } } if(irCount <= elevs.back().irOffset) { - ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n", - elevs.size()-1, elevs.back().irOffset, irCount); + ERR("Invalid evOffset: evOffset[{}]={} (irCount={})", elevs.size()-1, + elevs.back().irOffset, irCount); return nullptr; } @@ -572,16 +585,16 @@ std::unique_ptr LoadHrtf00(std::istream &data) elevs[i-1].azCount = static_cast(elevs[i].irOffset - elevs[i-1].irOffset); if(elevs[i-1].azCount < MinAzCount || elevs[i-1].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", - i-1, elevs[i-1].azCount, MinAzCount, MaxAzCount); + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i-1, elevs[i-1].azCount, + MinAzCount, MaxAzCount); return nullptr; } } elevs.back().azCount = static_cast(irCount - elevs.back().irOffset); if(elevs.back().azCount < MinAzCount || elevs.back().azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n", - elevs.size()-1, elevs.back().azCount, MinAzCount, MaxAzCount); + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", elevs.size()-1, + elevs.back().azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -589,7 +602,7 @@ std::unique_ptr LoadHrtf00(std::istream &data) auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) @@ -601,14 +614,14 @@ std::unique_ptr LoadHrtf00(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); const std::array field{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, static_cast(irSize), field, elevs, coeffs.data(), @@ -625,13 +638,13 @@ std::unique_ptr LoadHrtf01(std::istream &data) if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", - evCount, MinEvCount, MaxEvCount); + ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, + MaxEvCount); return nullptr; } @@ -645,7 +658,7 @@ std::unique_ptr LoadHrtf01(std::istream &data) { if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, elevs[i].azCount, + ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i, elevs[i].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -660,7 +673,7 @@ std::unique_ptr LoadHrtf01(std::istream &data) auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) @@ -672,14 +685,14 @@ std::unique_ptr LoadHrtf01(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); const std::array field{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, irSize, field, elevs, coeffs.data(), delays.data()); @@ -702,23 +715,23 @@ std::unique_ptr LoadHrtf02(std::istream &data) if(sampleType > SampleType_S24) { - ERR("Unsupported sample type: %d\n", sampleType); + ERR("Unsupported sample type: {}", sampleType); return nullptr; } if(channelType > ChanType_LeftRight) { - ERR("Unsupported channel type: %d\n", channelType); + ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { - ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } @@ -734,13 +747,13 @@ std::unique_ptr LoadHrtf02(std::istream &data) if(distance < MinFdDistance || distance > MaxFdDistance) { - ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } @@ -749,14 +762,14 @@ std::unique_ptr LoadHrtf02(std::istream &data) fields[f].evCount = evCount; if(f > 0 && fields[f].distance <= fields[f-1].distance) { - ERR("Field distance[%zu] is not after previous (%f > %f)\n", f, fields[f].distance, + ERR("Field distance[{}] is not after previous ({:f} > {:f})", f, fields[f].distance, fields[f-1].distance); return nullptr; } const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; @@ -765,7 +778,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e, + ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -790,7 +803,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } } @@ -798,7 +811,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } } @@ -811,14 +824,14 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { @@ -826,7 +839,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = float(readle(data)) / 32768.0f; val[1] = float(readle(data)) / 32768.0f; @@ -837,7 +850,7 @@ std::unique_ptr LoadHrtf02(std::istream &data) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -856,12 +869,12 @@ std::unique_ptr LoadHrtf02(std::istream &data) { if(delays[i][0] > MaxHrirDelay) { - ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MaxHrirDelay); + ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } if(delays[i][1] > MaxHrirDelay) { - ERR("Invalid delays[%zu][1]: %d (%d)\n", i, delays[i][1], MaxHrirDelay); + ERR("Invalid delays[{}][1]: {} ({})", i, delays[i][1], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; @@ -954,18 +967,18 @@ std::unique_ptr LoadHrtf03(std::istream &data) if(channelType > ChanType_LeftRight) { - ERR("Unsupported channel type: %d\n", channelType); + ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { - ERR("Unsupported HRIR size, irSize=%d (%d to %d)\n", irSize, MinIrLength, HrirLength); + ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { - ERR("Unsupported number of field-depths: fdCount=%d (%d to %d)\n", fdCount, MinFdCount, + ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } @@ -981,13 +994,13 @@ std::unique_ptr LoadHrtf03(std::istream &data) if(distance < MinFdDistance || distance > MaxFdDistance) { - ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance, + ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { - ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount, + ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } @@ -996,14 +1009,14 @@ std::unique_ptr LoadHrtf03(std::istream &data) fields[f].evCount = evCount; if(f > 0 && fields[f].distance > fields[f-1].distance) { - ERR("Field distance[%zu] is not before previous (%f <= %f)\n", f, fields[f].distance, - fields[f-1].distance); + ERR("Field distance[{}] is not before previous ({:f} <= {:f})", f, + fields[f].distance, fields[f-1].distance); return nullptr; } const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; @@ -1012,7 +1025,7 @@ std::unique_ptr LoadHrtf03(std::istream &data) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { - ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e, + ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } @@ -1035,7 +1048,7 @@ std::unique_ptr LoadHrtf03(std::istream &data) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } for(auto &val : delays) @@ -1047,20 +1060,20 @@ std::unique_ptr LoadHrtf03(std::istream &data) { if(delays[i][0] > MaxHrirDelay<{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -1078,14 +1091,14 @@ std::unique_ptr LoadHrtf03(std::istream &data) { if(delays[i][0] > MaxHrirDelay< MaxHrirDelay< bool - { return entry.mFilename == filename; }); + [filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %.*s\n", al::sizei(filename), filename.data()); + TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a - * format update). */ - size_t namepos{filename.rfind('/')+1}; - if(!namepos) namepos = filename.rfind('\\')+1; + * format update). + */ + const auto namepos = std::max(filename.rfind('/')+1, filename.rfind('\\')+1); + const auto extpos = filename.substr(namepos).rfind('.'); - size_t extpos{filename.rfind('.')}; - if(extpos <= namepos) extpos = std::string::npos; + const auto basename = (extpos == std::string::npos) ? + filename.substr(namepos) : filename.substr(namepos, extpos); - const std::string_view basename{(extpos == std::string::npos) ? - filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; - std::string newname{basename}; - int count{1}; + auto count = 1; + auto newname = std::string{basename}; while(checkName(newname)) - { - newname = basename; - newname += " #"; - newname += std::to_string(++count); - } - const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(newname, filename); + newname = fmt::format("{} #{}", basename, ++count); - TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str()); + const auto &entry = EnumeratedHrtfs.emplace_back(newname, filename); + TRACE("Adding file entry \"{}\"", entry.mFilename); } /* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer @@ -1142,32 +1149,26 @@ void AddFileEntry(const std::string_view filename) */ void AddBuiltInEntry(const std::string_view dispname, uint residx) { - std::string filename{'!'+std::to_string(residx)+'_'}; - filename += dispname; + auto filename = fmt::format("!{}_{}", residx, dispname); auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&filename](const HrtfEntry &entry) -> bool - { return entry.mFilename == filename; }); + [&filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ - std::string newname{dispname}; - int count{1}; + auto count = 1; + auto newname = std::string{dispname}; while(checkName(newname)) - { - newname = dispname; - newname += " #"; - newname += std::to_string(++count); - } - const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); + newname = fmt::format("{} #{}", dispname, ++count); - TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str()); + const auto &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); + TRACE("Adding built-in entry \"{}\"", entry.mFilename); } @@ -1201,6 +1202,9 @@ std::vector EnumerateHrtf(std::optional pathopt) std::lock_guard enumlock{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); + for(const auto &fname : SearchDataFiles(".mhr"sv)) + AddFileEntry(fname); + bool usedefaults{true}; if(pathopt) { @@ -1253,7 +1257,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate) try { if(devrate > MaxSampleRate) { - WARN("Device sample rate too large for HRTF (%uhz > %uhz)\n", devrate, MaxSampleRate); + WARN("Device sample rate too large for HRTF ({}hz > {}hz)", devrate, MaxSampleRate); return nullptr; } std::lock_guard enumlock{EnumeratedHrtfLock}; @@ -1283,13 +1287,14 @@ try { std::unique_ptr stream; int residx{}; char ch{}; + /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_') { - TRACE("Loading %s...\n", fname.c_str()); + TRACE("Loading {}...", fname); al::span res{GetResource(residx)}; if(res.empty()) { - ERR("Could not get resource %u, %.*s\n", residx, al::sizei(name), name.data()); + ERR("Could not get resource {}, {}", residx, name); return nullptr; } /* NOLINTNEXTLINE(*-const-cast) */ @@ -1297,44 +1302,44 @@ try { } else { - TRACE("Loading %s...\n", fname.c_str()); - auto fstr = std::make_unique(std::filesystem::u8path(fname), + TRACE("Loading {}...", fname); + auto fstr = std::make_unique(fs::u8path(fname), std::ios::binary); if(!fstr->is_open()) { - ERR("Could not open %s\n", fname.c_str()); + ERR("Could not open {}", fname); return nullptr; } stream = std::move(fstr); } - std::unique_ptr hrtf; - std::array magic{}; + auto hrtf = std::unique_ptr{}; + auto magic = std::array{}; stream->read(magic.data(), magic.size()); - if(stream->gcount() < static_cast(GetMarker03Name().size())) - ERR("%.*s data is too short (%zu bytes)\n", al::sizei(name),name.data(), stream->gcount()); + if(stream->gcount() < std::streamsize{magic.size()}) + ERR("{} data is too short ({} bytes)", name, stream->gcount()); else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v3\n"); + TRACE("Detected data set format v3"); hrtf = LoadHrtf03(*stream); } else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v2\n"); + TRACE("Detected data set format v2"); hrtf = LoadHrtf02(*stream); } else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v1\n"); + TRACE("Detected data set format v1"); hrtf = LoadHrtf01(*stream); } else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()}) { - TRACE("Detected data set format v0\n"); + TRACE("Detected data set format v0"); hrtf = LoadHrtf00(*stream); } else - ERR("Invalid header in %.*s: \"%.8s\"\n", al::sizei(name), name.data(), magic.data()); + ERR("Invalid header in {}: \"{}\"", name, std::string_view{magic.data(), magic.size()}); stream.reset(); if(!hrtf) @@ -1342,8 +1347,7 @@ try { if(hrtf->mSampleRate != devrate) { - TRACE("Resampling HRTF %.*s (%uhz -> %uhz)\n", al::sizei(name), name.data(), - hrtf->mSampleRate, devrate); + TRACE("Resampling HRTF {} ({}hz -> {}hz)", name, uint{hrtf->mSampleRate}, devrate); /* Calculate the last elevation's index and get the total IR count. */ const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz, @@ -1353,7 +1357,7 @@ try { const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount}; /* Resample all the IRs. */ - std::array,2> inout; + std::array,2> inout{}; PPhaseResampler rs; rs.init(hrtf->mSampleRate, devrate); for(size_t i{0};i < irCount;++i) @@ -1393,7 +1397,7 @@ try { float delay_scale{HrirDelayFracOne}; if(max_delay > MaxHrirDelay) { - WARN("Resampled delay exceeds max (%.2f > %d)\n", max_delay, MaxHrirDelay); + WARN("Resampled delay exceeds max ({:.2f} > {})", max_delay, MaxHrirDelay); delay_scale *= float{MaxHrirDelay} / max_delay; } @@ -1415,13 +1419,13 @@ try { } handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf)); - TRACE("Loaded HRTF %.*s for sample rate %uhz, %u-sample filter\n", al::sizei(name),name.data(), - handle->mEntry->mSampleRate, handle->mEntry->mIrSize); + 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 %.*s: %s\n", al::sizei(name), name.data(), e.what()); + ERR("Failed to load {}: {}", name, e.what()); return nullptr; } @@ -1429,13 +1433,13 @@ catch(std::exception& e) { void HrtfStore::add_ref() { auto ref = IncrementRef(mRef); - TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval()){this}, ref); + TRACE("HrtfStore {} increasing refcount to {}", decltype(std::declval()){this}, ref); } void HrtfStore::dec_ref() { auto ref = DecrementRef(mRef); - TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval()){this}, ref); + TRACE("HrtfStore {} decreasing refcount to {}", decltype(std::declval()){this}, ref); if(ref == 0) { std::lock_guard loadlock{LoadedHrtfLock}; @@ -1446,7 +1450,7 @@ void HrtfStore::dec_ref() HrtfStore *entry{hrtf.mEntry.get()}; if(entry && entry->mRef.load() == 0) { - TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.c_str()); + TRACE("Unloading unused HRTF {}", hrtf.mFilename); hrtf.mEntry = nullptr; return true; } diff --git a/libs/openal-soft/core/hrtf.h b/libs/openal-soft/core/hrtf.h index b0c769cc..020e3077 100644 --- a/libs/openal-soft/core/hrtf.h +++ b/libs/openal-soft/core/hrtf.h @@ -11,7 +11,6 @@ #include "almalloc.h" #include "alspan.h" -#include "atomic.h" #include "ambidefs.h" #include "bufferline.h" #include "flexarray.h" @@ -20,7 +19,7 @@ struct alignas(16) HrtfStore { - std::atomic mRef; + std::atomic mRef{}; uint mSampleRate : 24; uint mIrSize : 8; @@ -38,12 +37,12 @@ struct alignas(16) HrtfStore { ushort azCount; ushort irOffset; }; - Elevation *mElev; - const HrirArray *mCoeffs; - const ubyte2 *mDelays; + al::span mElev; + al::span mCoeffs; + al::span mDelays; - void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs, - const al::span delays) const; + void getCoeffs(float elevation, float azimuth, float distance, float spread, + const HrirSpan coeffs, const al::span delays) const; void add_ref(); void dec_ref(); @@ -75,7 +74,7 @@ struct DirectHrtfState { uint mIrSize{0}; al::FlexArray mChannels; - DirectHrtfState(size_t numchans) : mChannels{numchans} { } + explicit DirectHrtfState(size_t numchans) : mChannels{numchans} { } /** * Produces HRTF filter coefficients for decoding B-Format, given a set of * virtual speaker positions, a matching decoding matrix, and per-order diff --git a/libs/openal-soft/core/logging.cpp b/libs/openal-soft/core/logging.cpp index 91752a65..ba40b368 100644 --- a/libs/openal-soft/core/logging.cpp +++ b/libs/openal-soft/core/logging.cpp @@ -3,7 +3,6 @@ #include "logging.h" -#include #include #include #include @@ -11,9 +10,10 @@ #include #include #include -#include +#include +#include -#include "alspan.h" +#include "alstring.h" #include "strutils.h" @@ -35,6 +35,8 @@ LogLevel gLogLevel{LogLevel::Error}; namespace { +using namespace std::string_view_literals; + enum class LogState : uint8_t { FirstRun, Ready, @@ -76,57 +78,23 @@ void al_set_log_callback(LogCallbackFunc callback, void *userptr) } } -void al_print(LogLevel level, const char *fmt, ...) noexcept -try { - /* Kind of ugly since string literals are const char arrays with a size - * that includes the null terminator, which we want to exclude from the - * span. - */ - auto prefix = al::span{"[ALSOFT] (--) "}.first<14>(); +void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args) +{ + const auto msg = fmt::vformat(fmt, std::move(args)); + + auto prefix = "[ALSOFT] (--) "sv; switch(level) { case LogLevel::Disable: break; - case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break; - case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break; - case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break; + case LogLevel::Error: prefix = "[ALSOFT] (EE) "sv; break; + case LogLevel::Warning: prefix = "[ALSOFT] (WW) "sv; break; + case LogLevel::Trace: prefix = "[ALSOFT] (II) "sv; break; } - std::vector dynmsg; - std::array stcmsg{}; - - char *str{stcmsg.data()}; - auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin()); - al::span msg{prefend1, stcmsg.end()}; - - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - std::va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)}; - if(msglen >= 0) - { - if(static_cast(msglen) >= msg.size()) UNLIKELY - { - dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); - - str = dynmsg.data(); - auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); - msg = {prefend2, dynmsg.end()}; - - std::vsnprintf(msg.data(), msg.size(), fmt, args2); - } - msg = msg.first(static_cast(msglen)); - } - else - msg = {msg.data(), std::strlen(msg.data())}; - va_end(args2); - va_end(args); - /* NOLINTEND(*-array-to-pointer-decay) */ - if(gLogLevel >= level) { auto logfile = gLogFile; - fputs(str, logfile); + fmt::println(logfile, "{}{}", prefix, msg); fflush(logfile); } #if defined(_WIN32) && !defined(NDEBUG) @@ -134,8 +102,7 @@ try { * informational, warning, or error debug messages. So only print them for * non-Release builds. */ - std::wstring wstr{utf8_to_wstr(str)}; - OutputDebugStringW(wstr.c_str()); + OutputDebugStringW(utf8_to_wstr(fmt::format("{}{}\n", prefix, msg)).c_str()); #elif defined(__ANDROID__) auto android_severity = [](LogLevel l) noexcept { @@ -150,26 +117,20 @@ try { } return ANDROID_LOG_ERROR; }; - __android_log_print(android_severity(level), "openal", "%s", str); + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ + __android_log_print(android_severity(level), "openal", "%.*s%s", al::sizei(prefix), + prefix.data(), msg.c_str()); #endif auto cblock = std::lock_guard{LogCallbackMutex}; if(gLogState != LogState::Disable) { - while(!msg.empty() && std::isspace(msg.back())) - { - msg.back() = '\0'; - msg = msg.first(msg.size()-1); - } - if(auto logcode = GetLevelCode(level); logcode && !msg.empty()) + if(auto logcode = GetLevelCode(level)) { if(gLogCallback) - gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast(msg.size())); + gLogCallback(gLogCallbackPtr, *logcode, msg.data(), al::sizei(msg)); else if(gLogState == LogState::FirstRun) gLogState = LogState::Disable; } } } -catch(...) { - /* Swallow any exceptions */ -} diff --git a/libs/openal-soft/core/logging.h b/libs/openal-soft/core/logging.h index b8d5f583..f32fd43e 100644 --- a/libs/openal-soft/core/logging.h +++ b/libs/openal-soft/core/logging.h @@ -1,8 +1,9 @@ #ifndef CORE_LOGGING_H #define CORE_LOGGING_H -#include +#include +#include "fmt/core.h" #include "opthelpers.h" @@ -12,9 +13,9 @@ enum class LogLevel { Warning, Trace }; -extern LogLevel gLogLevel; +DECL_HIDDEN extern LogLevel gLogLevel; -extern FILE *gLogFile; +DECL_HIDDEN extern FILE *gLogFile; using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; @@ -22,12 +23,13 @@ using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, void al_set_log_callback(LogCallbackFunc callback, void *userptr); -#ifdef __MINGW32__ -[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] -#else -[[gnu::format(printf,2,3)]] -#endif -void al_print(LogLevel level, const char *fmt, ...) noexcept; +void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args); + +template +void al_print(LogLevel level, fmt::format_string fmt, Args&& ...args) noexcept +try { + al_print_impl(level, fmt, fmt::make_format_args(args...)); +} catch(...) { } #define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) diff --git a/libs/openal-soft/core/mastering.cpp b/libs/openal-soft/core/mastering.cpp index 468a286d..4da708ab 100644 --- a/libs/openal-soft/core/mastering.cpp +++ b/libs/openal-soft/core/mastering.cpp @@ -11,7 +11,6 @@ #include #include -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "opthelpers.h" @@ -20,7 +19,7 @@ /* These structures assume BufferLineSize is a power of 2. */ static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); -struct SlidingHold { +struct SIMDALIGN SlidingHold { alignas(16) FloatBufferLine mValues; std::array mExpiries; uint mLowerIndex; @@ -31,7 +30,9 @@ struct SlidingHold { namespace { -using namespace std::placeholders; +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } /* This sliding hold follows the input level with an instant attack and a * fixed duration hold before an instant release to the next highest level. @@ -101,21 +102,25 @@ void ShiftSlidingHold(SlidingHold *Hold, const uint n) /* Multichannel compression is linked via the absolute maximum of all * channels. */ -void Compressor::linkChannels(const uint SamplesToDo, const FloatBufferLine *OutBuffer) +void Compressor::linkChannels(const uint SamplesToDo, + const al::span OutBuffer) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const auto side_begin = mSideChain.begin() + mLookAhead; - std::fill(side_begin, side_begin+SamplesToDo, 0.0f); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::fill_n(sideChain.begin(), sideChain.size(), 0.0f); - auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void + auto fill_max = [sideChain](const FloatBufferLine &input) -> void { - const float *RESTRICT buffer{al::assume_aligned<16>(input.data())}; + const auto buffer = assume_aligned_span<16>(al::span{input}); auto max_abs = [](const float s0, const float s1) noexcept -> float { return std::max(s0, std::fabs(s1)); }; - std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); + std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(), + max_abs); }; - std::for_each(OutBuffer, OutBuffer+mNumChans, fill_max); + for(const FloatBufferLine &input : OutBuffer) + fill_max(input); } /* This calculates the squared crest factor of the control signal for the @@ -130,6 +135,7 @@ void Compressor::crestDetector(const uint SamplesToDo) float y2_rms{mLastRmsSq}; ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { @@ -139,8 +145,8 @@ void Compressor::crestDetector(const uint SamplesToDo) y2_rms = lerpf(x2, y2_rms, a_crest); return y2_peak / y2_rms; }; - const auto side_begin = mSideChain.begin() + mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, mCrestFactor.begin(), calc_crest); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest); mLastPeakSq = y2_peak; mLastRmsSq = y2_rms; @@ -153,10 +159,11 @@ void Compressor::crestDetector(const uint SamplesToDo) void Compressor::peakDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */ - const auto side_begin = mSideChain.begin() + mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), [](float s) { return std::log(std::max(0.000001f, s)); }); } @@ -167,6 +174,7 @@ void Compressor::peakDetector(const uint SamplesToDo) void Compressor::peakHoldDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); SlidingHold *hold{mHold.get()}; uint i{0}; @@ -175,8 +183,8 @@ void Compressor::peakHoldDetector(const uint SamplesToDo) const float x_G{std::log(std::max(0.000001f, x_abs))}; return UpdateSlidingHold(hold, i++, x_G); }; - auto side_begin = mSideChain.begin() + mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak); + auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak); ShiftSlidingHold(hold, SamplesToDo); } @@ -284,40 +292,40 @@ void Compressor::gainCompressor(const uint SamplesToDo) * reaching the offending impulse. This is best used when operating as a * limiter. */ -void Compressor::signalDelay(const uint SamplesToDo, FloatBufferLine *OutBuffer) +void Compressor::signalDelay(const uint SamplesToDo, const al::span OutBuffer) { - const size_t numChans{mNumChans}; - const uint lookAhead{mLookAhead}; + const auto lookAhead = mLookAhead; ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); ASSUME(lookAhead > 0); + ASSUME(lookAhead < BufferLineSize); - for(size_t c{0};c < numChans;c++) + auto delays = mDelay.begin(); + for(auto &buffer : OutBuffer) { - float *inout{al::assume_aligned<16>(OutBuffer[c].data())}; - float *delaybuf{al::assume_aligned<16>(mDelay[c].data())}; + const auto inout = al::span{buffer}.first(SamplesToDo); + const auto delaybuf = al::span{*(delays++)}.first(lookAhead); - auto inout_end = inout + SamplesToDo; - if(SamplesToDo >= lookAhead) LIKELY + if(SamplesToDo >= delaybuf.size()) LIKELY { - auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); - std::swap_ranges(inout, delay_end, delaybuf); + const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size()); + const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end()); + std::swap_ranges(inout.begin(), delay_end, delaybuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, delaybuf); - std::rotate(delaybuf, delay_start, delaybuf + lookAhead); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin()); + std::rotate(delaybuf.begin(), delay_start, delaybuf.end()); } } } std::unique_ptr Compressor::Create(const size_t NumChans, const float SampleRate, - const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain, - const bool AutoDeclip, const float LookAheadTime, const float HoldTime, const float PreGainDb, - const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, - const float AttackTime, const float ReleaseTime) + const FlagBits autoflags, const float LookAheadTime, const float HoldTime, + const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, + const float KneeDb, const float AttackTime, const float ReleaseTime) { const auto lookAhead = static_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1.0f)); @@ -325,12 +333,11 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa BufferLineSize-1.0f)); auto Comp = CompressorPtr{new Compressor{}}; - Comp->mNumChans = NumChans; - Comp->mAuto.Knee = AutoKnee; - Comp->mAuto.Attack = AutoAttack; - Comp->mAuto.Release = AutoRelease; - Comp->mAuto.PostGain = AutoPostGain; - Comp->mAuto.Declip = AutoPostGain && AutoDeclip; + 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; @@ -373,26 +380,24 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa Compressor::~Compressor() = default; -void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) +void Compressor::process(const uint SamplesToDo, const al::span InOut) { - const size_t numChans{mNumChans}; - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); const float preGain{mPreGain}; if(preGain != 1.0f) { auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - std::transform(buffer, buffer+SamplesToDo, buffer, + const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo)); + std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(), [preGain](const float s) noexcept { return s * preGain; }); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); + std::for_each(InOut.begin(), InOut.end(), apply_gain); } - linkChannels(SamplesToDo, OutBuffer); + linkChannels(SamplesToDo, InOut); if(mAuto.Attack || mAuto.Release) crestDetector(SamplesToDo); @@ -405,18 +410,18 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) gainCompressor(SamplesToDo); if(!mDelay.empty()) - signalDelay(SamplesToDo, OutBuffer); + signalDelay(SamplesToDo, InOut); - const auto sideChain = al::span{mSideChain}; - auto apply_comp = [SamplesToDo,sideChain](FloatBufferLine &input) noexcept -> void + const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo)); + auto apply_comp = [gains](const FloatBufferSpan inout) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - const float *gains{al::assume_aligned<16>(sideChain.data())}; - std::transform(gains, gains+SamplesToDo, buffer, buffer, - [](const float g, const float s) noexcept { return g * s; }); + const auto buffer = assume_aligned_span<16>(inout); + std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(), + std::multiplies{}); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); + for(const FloatBufferSpan inout : InOut) + apply_comp(inout); - auto side_begin = mSideChain.begin() + SamplesToDo; - std::copy(side_begin, side_begin+mLookAhead, mSideChain.begin()); + const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead); + std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin()); } diff --git a/libs/openal-soft/core/mastering.h b/libs/openal-soft/core/mastering.h index f2cca857..32f581db 100644 --- a/libs/openal-soft/core/mastering.h +++ b/libs/openal-soft/core/mastering.h @@ -2,12 +2,13 @@ #define CORE_MASTERING_H #include +#include #include -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "bufferline.h" +#include "opthelpers.h" #include "vector.h" struct SlidingHold; @@ -25,9 +26,7 @@ using uint = unsigned int; * * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ */ -class Compressor { - size_t mNumChans{0u}; - +class SIMDALIGN Compressor { struct AutoFlags { bool Knee : 1; bool Attack : 1; @@ -67,16 +66,21 @@ class Compressor { Compressor() = default; - void linkChannels(const uint SamplesToDo, const FloatBufferLine *OutBuffer); + void linkChannels(const uint SamplesToDo, const al::span OutBuffer); void crestDetector(const uint SamplesToDo); void peakDetector(const uint SamplesToDo); void peakHoldDetector(const uint SamplesToDo); void gainCompressor(const uint SamplesToDo); - void signalDelay(const uint SamplesToDo, FloatBufferLine *OutBuffer); + void signalDelay(const uint SamplesToDo, const al::span OutBuffer); public: + enum { + AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, FlagsCount + }; + using FlagBits = std::bitset; + ~Compressor(); - void process(const uint SamplesToDo, FloatBufferLine *OutBuffer); + void process(const uint SamplesToDo, al::span InOut); [[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; } /** @@ -106,11 +110,9 @@ public: * automating release time. */ static std::unique_ptr Create(const size_t NumChans, const float SampleRate, - const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, - const bool AutoPostGain, const bool AutoDeclip, const float LookAheadTime, - const float HoldTime, const float PreGainDb, const float PostGainDb, - const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, - const float ReleaseTime); + const FlagBits autoflags, const float LookAheadTime, const float HoldTime, + const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, + const float KneeDb, const float AttackTime, const float ReleaseTime); }; using CompressorPtr = std::unique_ptr; diff --git a/libs/openal-soft/core/mixer.cpp b/libs/openal-soft/core/mixer.cpp index 806ac8b8..bba7ae20 100644 --- a/libs/openal-soft/core/mixer.cpp +++ b/libs/openal-soft/core/mixer.cpp @@ -3,10 +3,12 @@ #include "mixer.h" +#include #include +#include #include "alnumbers.h" -#include "devformat.h" +#include "core/ambidefs.h" #include "device.h" #include "mixer/defs.h" @@ -85,9 +87,9 @@ std::array CalcAmbiCoeffs(const float y, const float z, c void ComputePanGains(const MixParams *mix, const al::span coeffs, const float ingain, const al::span gains) { - auto ambimap = mix->AmbiMap.cbegin(); + auto ambimap = al::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size()); - auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(), + auto iter = std::transform(ambimap.begin(), ambimap.end(), gains.begin(), [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }); std::fill(iter, gains.end(), 0.0f); diff --git a/libs/openal-soft/core/mixer.h b/libs/openal-soft/core/mixer.h index 9062ebac..ec6c3271 100644 --- a/libs/openal-soft/core/mixer.h +++ b/libs/openal-soft/core/mixer.h @@ -3,34 +3,33 @@ #include #include -#include -#include +#include #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" -#include "devformat.h" +#include "opthelpers.h" struct MixParams; /* Mixer functions that handle one input and multiple output channels. */ using MixerOutFunc = void(*)(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos); + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos); -extern MixerOutFunc MixSamplesOut; +DECL_HIDDEN extern MixerOutFunc MixSamplesOut; inline void MixSamples(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos) + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos) { MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); } /* Mixer functions that handle one input and one output channel. */ -using MixerOneFunc = void(*)(const al::span InSamples, float *OutBuffer, - float &CurrentGain, const float TargetGain, const size_t Counter); +using MixerOneFunc = void(*)(const al::span InSamples,const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter); -extern MixerOneFunc MixSamplesOne; -inline void MixSamples(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +DECL_HIDDEN extern MixerOneFunc MixSamplesOne; +inline void MixSamples(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter) { MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } diff --git a/libs/openal-soft/core/mixer/defs.h b/libs/openal-soft/core/mixer/defs.h index ba10a146..4506dde6 100644 --- a/libs/openal-soft/core/mixer/defs.h +++ b/libs/openal-soft/core/mixer/defs.h @@ -30,13 +30,16 @@ inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ enum class Resampler : std::uint8_t { Point, Linear, - Cubic, + Spline, + Gaussian, FastBSinc12, BSinc12, FastBSinc24, BSinc24, + FastBSinc48, + BSinc48, - Max = BSinc24 + Max = BSinc48 }; /* Interpolator state. Kind of a misnomer since the interpolator itself is @@ -51,7 +54,7 @@ struct BsincState { * delta coefficients. Starting at phase index 0, each subsequent phase * index follows contiguously. */ - const float *filter; + al::span filter; }; struct CubicState { @@ -59,49 +62,51 @@ struct CubicState { * each subsequent phase index follows contiguously. */ al::span filter; - CubicState(al::span f) : filter{f} { } + explicit CubicState(al::span f) : filter{f} { } }; using InterpState = std::variant; -using ResamplerFunc = void(*)(const InterpState *state, const float *src, uint frac, +using ResamplerFunc = void(*)(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); template -void Resample_(const InterpState *state, const float *src, uint frac, const uint increment, - const al::span dst); +void Resample_(const InterpState *state, const al::span src, uint frac, + const uint increment, const al::span dst); template void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos); + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos); template -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter); +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter); template -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo); template -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize); +void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo); template void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); /* Vectorized resampler helpers */ template -constexpr void InitPosArrays(uint frac, const uint increment, const al::span frac_arr, - const al::span pos_arr) +constexpr void InitPosArrays(uint pos, uint frac, const uint increment, + const al::span frac_arr, const al::span pos_arr) { static_assert(pos_arr.size() == frac_arr.size()); - pos_arr[0] = 0; + pos_arr[0] = pos; frac_arr[0] = frac; - for(size_t i{1};i < pos_arr.size();i++) + for(size_t i{1};i < pos_arr.size();++i) { const uint frac_tmp{frac_arr[i-1] + increment}; pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits); diff --git a/libs/openal-soft/core/mixer/hrtfbase.h b/libs/openal-soft/core/mixer/hrtfbase.h index 868f09d2..703bfab9 100644 --- a/libs/openal-soft/core/mixer/hrtfbase.h +++ b/libs/openal-soft/core/mixer/hrtfbase.h @@ -4,21 +4,23 @@ #include #include -#include "almalloc.h" +#include "defs.h" #include "hrtfdefs.h" #include "opthelpers.h" using uint = unsigned int; -using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize, +using ApplyCoeffsT = void(const al::span Values, const size_t irSize, const ConstHrirSpan Coeffs, const float left, const float right); template -inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) +inline void MixHrtfBase(const al::span InSamples, const al::span AccumSamples, + const size_t IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; const float gainstep{hrtfparams->GainStep}; @@ -27,26 +29,28 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]}; float stepcount{0.0f}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { const float g{gain + gainstep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, left, right); stepcount += 1.0f; } } template -inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples, - const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize) +inline void MixHrtfBlendBase(const al::span InSamples, + const al::span AccumSamples, const size_t IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; - const float oldGainStep{oldparams->Gain / static_cast(BufferSize)}; + const float oldGainStep{oldparams->Gain / static_cast(SamplesToDo)}; const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; @@ -54,29 +58,29 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl { size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; - auto stepcount = static_cast(BufferSize); - for(size_t i{0u};i < BufferSize;++i) + auto stepcount = static_cast(SamplesToDo); + for(size_t i{0u};i < SamplesToDo;++i) { const float g{oldGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right); stepcount -= 1.0f; } } - if(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) LIKELY + if(newGainStep*static_cast(SamplesToDo) > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; float stepcount{1.0f}; - for(size_t i{1u};i < BufferSize;++i) + for(size_t i{1u};i < SamplesToDo;++i) { const float g{newGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right); stepcount += 1.0f; } @@ -85,46 +89,52 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl template inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *RESTRICT AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChannelState, + const size_t IrSize, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); + assert(ChannelState.size() == InSamples.size()); + auto ChanState = ChannelState.begin(); for(const FloatBufferLine &input : InSamples) { /* For dual-band processing, the signal needs extra scaling applied to * the high frequency response. The band-splitter applies this scaling * with a consistent phase shift regardless of the scale amount. */ - ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf, + ChanState->mSplitter.processHfScale(al::span{input}.first(SamplesToDo), TempBuf, ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf.data())}; const ConstHrirSpan Coeffs{ChanState->mCoeffs}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { - const float insample{tempbuf[i]}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample); + const float insample{TempBuf[i]}; + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, insample, insample); } ++ChanState; } /* Add the HRTF signal to the existing "direct" signal. */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; - float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; - for(size_t i{0u};i < BufferSize;++i) - left[i] += AccumSamples[i][0]; - for(size_t i{0u};i < BufferSize;++i) - right[i] += AccumSamples[i][1]; + const auto left = al::span{al::assume_aligned<16>(LeftOut.data()), SamplesToDo}; + std::transform(left.cbegin(), left.cend(), AccumSamples.cbegin(), left.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[0]; }); + const auto right = al::span{al::assume_aligned<16>(RightOut.data()), SamplesToDo}; + std::transform(right.cbegin(), right.cend(), AccumSamples.cbegin(), right.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[1]; }); /* Copy the new in-progress accumulation values to the front and clear the * following samples for the next mix. */ - auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength, AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); + const auto accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength); + auto accum_iter = std::copy(accum_inprog.cbegin(), accum_inprog.cend(), AccumSamples.begin()); + std::fill_n(accum_iter, SamplesToDo, float2{}); } #endif /* CORE_MIXER_HRTFBASE_H */ diff --git a/libs/openal-soft/core/mixer/mixer_c.cpp b/libs/openal-soft/core/mixer/mixer_c.cpp index 5efa9cc3..e14454d2 100644 --- a/libs/openal-soft/core/mixer/mixer_c.cpp +++ b/libs/openal-soft/core/mixer/mixer_c.cpp @@ -12,6 +12,7 @@ #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" @@ -34,214 +35,246 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; -constexpr -auto do_point(const float *vals, const uint) noexcept -> float { return vals[0]; } -constexpr -auto do_lerp(const float *vals, const uint frac) noexcept -> float -{ return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } -constexpr -auto do_cubic(const CubicState &istate, const float *vals, const uint frac) noexcept -> float +using SamplerNST = float(const al::span, const size_t, const uint) noexcept; + +template +using SamplerT = float(const T&,const al::span,const size_t,const uint) noexcept; + +[[nodiscard]] constexpr +auto do_point(const al::span vals, const size_t pos, const uint) noexcept -> float +{ return vals[pos]; } +[[nodiscard]] constexpr +auto do_lerp(const al::span vals, const size_t pos, const uint frac) noexcept -> float +{ return lerpf(vals[pos+0], vals[pos+1], static_cast(frac)*(1.0f/MixerFracOne)); } +[[nodiscard]] constexpr +auto do_cubic(const CubicState &istate, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { /* Calculate the phase index and factor. */ - const uint pi{frac >> CubicPhaseDiffBits}; + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const auto fil = al::span{istate.filter[pi].mCoeffs}; const auto phd = al::span{istate.filter[pi].mDeltas}; /* Apply the phase interpolated filter. */ - return (fil[0] + pf*phd[0])*vals[0] + (fil[1] + pf*phd[1])*vals[1] - + (fil[2] + pf*phd[2])*vals[2] + (fil[3] + pf*phd[3])*vals[3]; + 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]; } -constexpr -auto do_bsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float +[[nodiscard]] constexpr +auto do_fastbsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { - const size_t m{istate.m}; + const size_t m{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - const float *fil{istate.filter + m*pi*2_uz}; - const float *phd{fil + m}; - const float *scd{fil + BSincPhaseCount*2_uz*m}; - const float *spd{scd + m}; - - /* Apply the scale and phase interpolated filter. */ - float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + istate.sf*scd[j_f] + pf*(phd[j_f] + istate.sf*spd[j_f])) * vals[j_f]; - return r; -} -constexpr -auto do_fastbsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float -{ - const size_t m{istate.m}; - ASSUME(m > 0); - - /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; - const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - - const float *fil{istate.filter + m*pi*2_uz}; - const float *phd{fil + m}; + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); /* Apply the phase interpolated filter. */ float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + pf*phd[j_f]) * vals[j_f]; + for(size_t j_f{0};j_f < m;++j_f) + r += (fil[j_f] + pf*phd[j_f]) * vals[pos+j_f]; + return r; +} +[[nodiscard]] constexpr +auto do_bsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float +{ + const size_t m{bsinc.m}; + ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); + + /* Calculate the phase index and factor. */ + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); + const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; + + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(BSincPhaseCount*2_uz*m); + const auto spd = scd.subspan(m); + + /* Apply the scale and phase interpolated filter. */ + float r{0.0f}; + for(size_t j_f{0};j_f < m;++j_f) + r += (fil[j_f] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+j_f]; return r; } -template -void DoResample(const float *src, uint frac, const uint increment, const al::span dst) -{ - ASSUME(frac < MixerFracOne); - std::generate(dst.begin(), dst.end(), [&src,&frac,increment]() -> float - { - const float output{Sampler(src, frac)}; - frac += increment; - src += frac>>MixerFracBits; - frac &= MixerFracMask; - return output; - }); -} - -template -void DoResample(const U istate, const float *src, uint frac, const uint increment, +template +void DoResample(const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - std::generate(dst.begin(), dst.end(), [istate,&src,&frac,increment]() -> float + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float { - const float output{Sampler(istate, src, frac)}; + const float output{Sampler(src, pos, frac)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } -constexpr void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, +template Sampler> +void DoResample(const U istate, const al::span src, uint frac, const uint increment, + const al::span dst) +{ + ASSUME(frac < MixerFracOne); + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> float + { + const float output{Sampler(istate, src, pos, frac)}; + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); +} + +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) noexcept { ASSUME(IrSize >= MinIrLength); - for(size_t c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } + ASSUME(IrSize <= HrirLength); + + auto mix_impulse = [left,right](const float2 &value, const float2 &coeff) noexcept -> float2 + { return float2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; }; + std::transform(Values.cbegin(), Values.cbegin()+ptrdiff_t(IrSize), Coeffs.cbegin(), + Values.begin(), mix_impulse); } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, +force_inline void MixLine(al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const float step{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto output = dst.begin(); + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; - for(;pos != min_len;++pos) - { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; - } - CurrentGain = gain; + auto input = InSamples.first(fade_len); + InSamples = InSamples.subspan(fade_len); - if(!(std::abs(gain) > GainSilenceThreshold)) + const float gain{CurrentGain}; + float step_count{0.0f}; + output = std::transform(input.begin(), input.end(), output, output, + [gain,step,&step_count](const float in, float out) noexcept -> float + { + out += in * (gain + step*step_count); + step_count += 1.0f; + return out; + }); + + if(fade_len < Counter) + { + CurrentGain = gain + step*step_count; + return; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; - for(;pos != InSamples.size();++pos) - dst[pos] += InSamples[pos] * gain; + + std::transform(InSamples.begin(), InSamples.end(), output, output, + [TargetGain](const float in, const float out) noexcept -> float + { return out + in*TargetGain; }); } } // namespace template<> -void Resample_(const InterpState*, const float *src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(src, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState*, const float *src, uint frac, const uint increment, - const al::span dst) -{ DoResample(src, frac, increment, dst); } - -template<> -void Resample_(const InterpState *state, const float *src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(std::get(*state), src-1, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *src, uint frac, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { - const auto istate = std::get(*state); - DoResample(istate, src-istate.l, frac, increment, dst); + DoResample(std::get(*state), src.subspan(MaxResamplerEdge-1), + frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *src, uint frac, +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + const auto istate = std::get(*state); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); +} + +template<> +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto istate = std::get(*state); - DoResample(istate, src-istate.l, frac, increment, dst); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); } template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples,const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, - TargetGain, delta, min_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_neon.cpp b/libs/openal-soft/core/mixer/mixer_neon.cpp index b44b7830..600c014b 100644 --- a/libs/openal-soft/core/mixer/mixer_neon.cpp +++ b/libs/openal-soft/core/mixer/mixer_neon.cpp @@ -14,10 +14,12 @@ #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" +struct CTag; struct NEONTag; struct LerpTag; struct CubicTag; @@ -63,60 +65,62 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3) return ret; } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - auto dup_samples = [left,right] + ASSUME(IrSize >= MinIrLength); + ASSUME(IrSize <= HrirLength); + + auto dup_samples = [left,right]() -> float32x4_t { float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)}; return vcombine_f32(leftright2, leftright2); }; - const float32x4_t leftright4{dup_samples()}; + const auto leftright4 = dup_samples(); - ASSUME(IrSize >= MinIrLength); + /* Using a loop here instead of std::transform since some builds seem to + * have an issue with accessing an array/span of float32x4_t. + */ for(size_t c{0};c < IrSize;c += 2) { - float32x4_t vals = vld1q_f32(&Values[c][0]); - float32x4_t coefs = vld1q_f32(&Coeffs[c][0]); - - vals = vmlaq_f32(vals, coefs, leftright4); - + auto vals = vld1q_f32(&Values[c][0]); + vals = vmlaq_f32(vals, vld1q_f32(&Coeffs[c][0]), leftright4); vst1q_f32(&Values[c][0], vals); } } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto pos = size_t{0}; + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = float{CurrentGain}; + auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 2}) { - const float32x4_t four4{vdupq_n_f32(4.0f)}; - const float32x4_t step4{vdupq_n_f32(step)}; - const float32x4_t gain4{vdupq_n_f32(gain)}; - float32x4_t step_count4{vdupq_n_f32(0.0f)}; - step_count4 = vsetq_lane_f32(1.0f, step_count4, 1); - step_count4 = vsetq_lane_f32(2.0f, step_count4, 2); - step_count4 = vsetq_lane_f32(3.0f, step_count4, 3); + const auto four4 = vdupq_n_f32(4.0f); + const auto step4 = vdupq_n_f32(step); + const auto gain4 = vdupq_n_f32(gain); + auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f); + + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const float32x4_t val4, float32x4_t dry4) + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); + step_count4 = vaddq_f32(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); /* 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. @@ -124,43 +128,70 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = vgetq_lane_f32(step_count4, 0); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) - return; - if(size_t todo{(InSamples.size()-pos) >> 2}) - { - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) + return; + if(const size_t todo{(InSamples.size()-pos) >> 2}) + { + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast(out.data()), out.size()/4}; + + const auto gain4 = vdupq_n_f32(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const float32x4_t val4, const float32x4_t dry4) -> float32x4_t + { return vmlaq_f32(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState*, const float *src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -169,18 +200,19 @@ void Resample_(const InterpState*, const float *src, uint frac, const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask); - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); uint32x4_t frac4 = vld1q_u32(frac_.data()); uint32x4_t pos4 = vld1q_u32(pos_.data()); - auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t { const uint pos0{vgetq_lane_u32(pos4, 0)}; const uint pos1{vgetq_lane_u32(pos4, 1)}; const uint pos2{vgetq_lane_u32(pos4, 2)}; const uint pos3{vgetq_lane_u32(pos4, 3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])}; const float32x4_t val2{set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; @@ -197,24 +229,26 @@ void Resample_(const InterpState*, const float *src, uint frac, if(size_t todo{dst.size()&3}) { - src += vgetq_lane_u32(pos4, 0); + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment] + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] { - const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; + const float output{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - return out; + return output; }); } } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -225,23 +259,23 @@ void Resample_(const InterpState *state, const float *src, uin const float32x4_t fracDiffOne4{vdupq_n_f32(1.0f/CubicPhaseDiffOne)}; const uint32x4_t fracDiffMask4{vdupq_n_u32(CubicPhaseDiffMask)}; - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); uint32x4_t frac4{vld1q_u32(frac_.data())}; uint32x4_t pos4{vld1q_u32(pos_.data())}; - src -= 1; - auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; - std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const uint pos0{vgetq_lane_u32(pos4, 0)}; const uint pos1{vgetq_lane_u32(pos4, 1)}; const uint pos2{vgetq_lane_u32(pos4, 2)}; const uint pos3{vgetq_lane_u32(pos4, 3)}; - const float32x4_t val0{vld1q_f32(src+pos0)}; - const float32x4_t val1{vld1q_f32(src+pos1)}; - const float32x4_t val2{vld1q_f32(src+pos2)}; - const float32x4_t val3{vld1q_f32(src+pos3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const float32x4_t val0{vld1q_f32(&src[pos0])}; + const float32x4_t val1{vld1q_f32(&src[pos1])}; + const float32x4_t val2{vld1q_f32(&src[pos2])}; + const float32x4_t val3{vld1q_f32(&src[pos3])}; const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)}; const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount); @@ -276,10 +310,11 @@ void Resample_(const InterpState *state, const float *src, uin if(const size_t todo{dst.size()&3}) { - src += vgetq_lane_u32(pos4, 0); + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter] + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; @@ -287,13 +322,13 @@ void Resample_(const InterpState *state, const float *src, uin const float32x4_t f4{vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4, vld1q_f32(filter[pi].mDeltas.data()))}; - float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))}; + float32x4_t r4{vmulq_f32(f4, vld1q_f32(&src[pos]))}; r4 = vaddq_f32(r4, vrev64q_f32(r4)); const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); @@ -301,31 +336,34 @@ void Resample_(const InterpState *state, const float *src, uin } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); - const float *const filter{bsinc.filter}; - const float32x4_t sf4{vdupq_n_f32(bsinc.sf)}; - const size_t m{bsinc.m}; + const auto sf4 = vdupq_n_f32(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= bsinc.l; - std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *fil{filter + m*pi*2_uz}; - const float *phd{fil + m}; - const float *scd{fil + BSincPhaseCount*2_uz*m}; - const float *spd{scd + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -335,7 +373,7 @@ void Resample_(const InterpState *state, const float *src, uin 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[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } @@ -343,35 +381,38 @@ void Resample_(const InterpState *state, const float *src, uin const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> -void Resample_(const InterpState *state, const float *src, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); - const float *const filter{bsinc.filter}; - const size_t m{bsinc.m}; + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= bsinc.l; - std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float + const auto filter = bsinc.filter.first(2_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *fil{filter + m*pi*2_uz}; - const float *phd{fil + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -379,7 +420,7 @@ void Resample_(const InterpState *state, const float *src, /* f = fil + pf*phd */ const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j])); /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } @@ -387,7 +428,7 @@ void Resample_(const InterpState *state, const float *src, const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); @@ -395,50 +436,59 @@ void Resample_(const InterpState *state, const float *src, template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> -void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +void Mix_(const al::span InSamples,const al::span OutBuffer, + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { - const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); - const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + if((OutPos&3) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { - const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); - const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_sse.cpp b/libs/openal-soft/core/mixer/mixer_sse.cpp index 74e13a31..097cd933 100644 --- a/libs/openal-soft/core/mixer/mixer_sse.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse.cpp @@ -16,10 +16,12 @@ #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" +struct CTag; struct SSETag; struct CubicTag; struct BSincTag; @@ -43,40 +45,45 @@ constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept { return _mm_add_ps(x, _mm_mul_ps(y, z)); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - ASSUME(IrSize >= MinIrLength); + ASSUME(IrSize <= HrirLength); + const auto lrlr = _mm_setr_ps(left, right, left, right); + /* Round up the IR size to a multiple of 2 for SIMD (2 IRs for 2 channels + * is 4 floats), to avoid cutting the last sample for odd IR counts. The + * underlying HRIR is a fixed-size multiple of 2, any extra samples are + * either 0 (silence) or more IR samples that get applied for "free". + */ + const auto count4 = size_t{(IrSize+1) >> 1}; + /* This isn't technically correct to test alignment, but it's true for * systems that support SSE, which is the only one that needs to know the * alignment of Values (which alternates between 8- and 16-byte aligned). */ - if(!(reinterpret_cast(Values)&15)) + if(!(reinterpret_cast(Values.data())&15)) { - for(size_t i{0};i < IrSize;i += 2) - { - const __m128 coeffs{_mm_load_ps(Coeffs[i].data())}; - __m128 vals{_mm_load_ps(Values[i].data())}; - vals = vmadd(vals, lrlr, coeffs); - _mm_store_ps(Values[i].data(), vals); - } + const auto vals4 = al::span{reinterpret_cast<__m128*>(Values[0].data()), count4}; + const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; + + std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), + [lrlr](const __m128 &val, const __m128 &coeff) -> __m128 + { return vmadd(val, coeff, lrlr); }); } else { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(Coeffs[0].data())}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()))}; - imp0 = _mm_mul_ps(lrlr, coeffs); + auto coeffs = _mm_load_ps(Coeffs[0].data()); + auto vals = _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data())); + auto imp0 = _mm_mul_ps(lrlr, coeffs); vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals); - size_t td{((IrSize+1)>>1) - 1}; + size_t td{count4 - 1}; size_t i{1}; do { coeffs = _mm_load_ps(Coeffs[i+1].data()); vals = _mm_load_ps(Values[i].data()); - imp1 = _mm_mul_ps(lrlr, coeffs); + const auto imp1 = _mm_mul_ps(lrlr, coeffs); imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); vals = _mm_add_ps(imp0, vals); _mm_store_ps(Values[i].data(), vals); @@ -90,37 +97,38 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons } } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = CurrentGain; + auto step_count = 0.0f; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 2}) { - const __m128 four4{_mm_set1_ps(4.0f)}; - const __m128 step4{_mm_set1_ps(step)}; - const __m128 gain4{_mm_set1_ps(gain)}; - __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; - do { - const __m128 val4{_mm_load_ps(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; + const auto four4 = _mm_set1_ps(4.0f); + const auto step4 = _mm_set1_ps(step); + const auto gain4 = _mm_set1_ps(gain); + auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f); - /* dry += val * (gain + step*step_count) */ - dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const __m128 val4, __m128 dry4) -> __m128 + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); + step_count4 = _mm_add_ps(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - _mm_store_ps(&dst[pos], dry4); - step_count4 = _mm_add_ps(step_count4, four4); - pos += 4; - } while(--todo); /* 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. @@ -128,51 +136,78 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = _mm_cvtss_f32(step_count4); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(size_t todo{(InSamples.size()-pos) >> 2}) { - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&dst[pos], dry4); - pos += 4; - } while(--todo); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast<__m128*>(out.data()), out.size()/4}; + + const auto gain4 = _mm_set1_ps(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const __m128 val4, const __m128 dry4) -> __m128 + { return vmadd(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const auto filter = std::get(*state).filter; - src -= 1; - std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter]() -> float + size_t pos{MaxResamplerEdge-1}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; @@ -184,47 +219,50 @@ void Resample_(const InterpState *state, const float *src, uint const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); /* r = f*src */ - __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))}; + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); - const float *const filter{bsinc.filter}; - const __m128 sf4{_mm_set1_ps(bsinc.sf)}; - const size_t m{bsinc.m}; + const auto sf4 = _mm_set1_ps(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= bsinc.l; - std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *fil{filter + m*pi*2_uz}; - const float *phd{fil + m}; - const float *scd{fil + BSincPhaseCount*2_uz*m}; - const float *spd{scd + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ @@ -232,61 +270,64 @@ void Resample_(const InterpState *state, const float *src, uint 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[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - const float output{_mm_cvtss_f32(r4)}; + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); - const float *const filter{bsinc.filter}; - const size_t m{bsinc.m}; + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= bsinc.l; - std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float + const auto filter = bsinc.filter.first(2_uz*m*BSincPhaseCount); + + ASSUME(bsinc.l <= MaxResamplerEdge); + size_t pos{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *fil{filter + m*pi*2_uz}; - const float *phd{fil + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*m*pi); + const auto phd = fil.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = fil + pf*phd */ - const __m128 f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); + const auto f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); /* r += f*src */ - r4 = vmadd(r4, f4, _mm_loadu_ps(&src[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - const float output{_mm_cvtss_f32(r4)}; + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); @@ -294,50 +335,59 @@ void Resample_(const InterpState *state, const float *src, template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, - const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { - const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); - const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + if((OutPos&3) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { - const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = std::min(Counter, InSamples.size()); - const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY + return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_sse2.cpp b/libs/openal-soft/core/mixer/mixer_sse2.cpp index 5108ee56..c79d50ca 100644 --- a/libs/openal-soft/core/mixer/mixer_sse2.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse2.cpp @@ -31,6 +31,7 @@ #include "alnumeric.h" #include "alspan.h" #include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "opthelpers.h" @@ -57,7 +58,7 @@ force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexce } // namespace template<> -void Resample_(const InterpState*, const float *src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -66,20 +67,21 @@ void Resample_(const InterpState*, const float *src, uint frac, const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 { const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; @@ -96,24 +98,26 @@ void Resample_(const InterpState*, const float *src, uint frac, if(size_t todo{dst.size()&3}) { - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment]() + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]() { - const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - return out; + return smp; }); } } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -124,25 +128,25 @@ void Resample_(const InterpState *state, const float *src, uin const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - src -= 1; - auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; - std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); - const __m128 val0{_mm_loadu_ps(src+pos0)}; - const __m128 val1{_mm_loadu_ps(src+pos1)}; - const __m128 val2{_mm_loadu_ps(src+pos2)}; - const __m128 val3{_mm_loadu_ps(src+pos3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; const auto pi0 = static_cast(_mm_cvtsi128_si32(pi4)); @@ -183,10 +187,11 @@ void Resample_(const InterpState *state, const float *src, uin if(const size_t todo{dst.size()&3}) { - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter] + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; @@ -194,14 +199,14 @@ void Resample_(const InterpState *state, const float *src, uin const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); - __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))}; + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); diff --git a/libs/openal-soft/core/mixer/mixer_sse41.cpp b/libs/openal-soft/core/mixer/mixer_sse41.cpp index 4a834d59..34533045 100644 --- a/libs/openal-soft/core/mixer/mixer_sse41.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse41.cpp @@ -32,6 +32,7 @@ #include "alnumeric.h" #include "alspan.h" #include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "opthelpers.h" @@ -58,7 +59,7 @@ force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexce } // namespace template<> -void Resample_(const InterpState*, const float *src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -67,20 +68,21 @@ void Resample_(const InterpState*, const float *src, uint frac, const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; - std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; @@ -101,24 +103,26 @@ void Resample_(const InterpState*, const float *src, uint frac, * four samples, so the lowest element is the next position to * resample. */ - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment] + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] { - const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - return out; + return smp; }); } } template<> -void Resample_(const InterpState *state, const float *src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -129,25 +133,25 @@ void Resample_(const InterpState *state, const float *src, uin const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; - alignas(16) std::array pos_, frac_; - InitPosArrays(frac, increment, al::span{frac_}, al::span{pos_}); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - src -= 1; - auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; - std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); - const __m128 val0{_mm_loadu_ps(src+pos0)}; - const __m128 val1{_mm_loadu_ps(src+pos1)}; - const __m128 val2{_mm_loadu_ps(src+pos2)}; - const __m128 val3{_mm_loadu_ps(src+pos3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; const auto pi0 = static_cast(_mm_extract_epi32(pi4, 0)); @@ -188,10 +192,11 @@ void Resample_(const InterpState *state, const float *src, uin if(const size_t todo{dst.size()&3}) { - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment,filter] + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; @@ -199,14 +204,14 @@ void Resample_(const InterpState *state, const float *src, uin const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); - __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))}; + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); diff --git a/libs/openal-soft/core/rtkit.cpp b/libs/openal-soft/core/rtkit.cpp index 70b5e12b..717059ab 100644 --- a/libs/openal-soft/core/rtkit.cpp +++ b/libs/openal-soft/core/rtkit.cpp @@ -69,6 +69,7 @@ namespace { inline pid_t _gettid() { #ifdef __linux__ + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ return static_cast(syscall(SYS_gettid)); #elif defined(__FreeBSD__) long pid{}; diff --git a/libs/openal-soft/core/storage_formats.cpp b/libs/openal-soft/core/storage_formats.cpp index 51f64644..b62f91a0 100644 --- a/libs/openal-soft/core/storage_formats.cpp +++ b/libs/openal-soft/core/storage_formats.cpp @@ -4,45 +4,49 @@ #include "storage_formats.h" #include +#include +namespace { +using namespace std::string_view_literals; +} // namespace -const char *NameFromFormat(FmtType type) noexcept +auto NameFromFormat(FmtType type) noexcept -> std::string_view { switch(type) { - case FmtUByte: return "UInt8"; - case FmtShort: return "Int16"; - case FmtInt: return "Int32"; - case FmtFloat: return "Float"; - case FmtDouble: return "Double"; - case FmtMulaw: return "muLaw"; - case FmtAlaw: return "aLaw"; - case FmtIMA4: return "IMA4 ADPCM"; - case FmtMSADPCM: return "MS ADPCM"; + 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 ""; + return ""sv; } -const char *NameFromFormat(FmtChannels channels) noexcept +auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view { switch(channels) { - case FmtMono: return "Mono"; - case FmtStereo: return "Stereo"; - case FmtRear: return "Rear"; - case FmtQuad: return "Quadraphonic"; - case FmtX51: return "Surround 5.1"; - case FmtX61: return "Surround 6.1"; - case FmtX71: return "Surround 7.1"; - case FmtBFormat2D: return "B-Format 2D"; - case FmtBFormat3D: return "B-Format 3D"; - case FmtUHJ2: return "UHJ2"; - case FmtUHJ3: return "UHJ3"; - case FmtUHJ4: return "UHJ4"; - case FmtSuperStereo: return "Super Stereo"; - case FmtMonoDup: return "Mono (dup)"; + case FmtMono: return "Mono"sv; + case FmtStereo: return "Stereo"sv; + case FmtRear: return "Rear"sv; + case FmtQuad: return "Quadraphonic"sv; + case FmtX51: return "Surround 5.1"sv; + case FmtX61: return "Surround 6.1"sv; + case FmtX71: return "Surround 7.1"sv; + case FmtBFormat2D: return "B-Format 2D"sv; + case FmtBFormat3D: return "B-Format 3D"sv; + case FmtUHJ2: return "UHJ2"sv; + case FmtUHJ3: return "UHJ3"sv; + case FmtUHJ4: return "UHJ4"sv; + case FmtSuperStereo: return "Super Stereo"sv; + case FmtMonoDup: return "Mono (dup)"sv; } - return ""; + return ""sv; } uint BytesFromFmt(FmtType type) noexcept diff --git a/libs/openal-soft/core/storage_formats.h b/libs/openal-soft/core/storage_formats.h index acced258..fab0a04c 100644 --- a/libs/openal-soft/core/storage_formats.h +++ b/libs/openal-soft/core/storage_formats.h @@ -1,6 +1,8 @@ #ifndef CORE_STORAGE_FORMATS_H #define CORE_STORAGE_FORMATS_H +#include + using uint = unsigned int; /* Storable formats */ @@ -43,8 +45,8 @@ enum class AmbiScaling : unsigned char { UHJ, }; -const char *NameFromFormat(FmtType type) noexcept; -const char *NameFromFormat(FmtChannels channels) noexcept; +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; diff --git a/libs/openal-soft/core/uhjfilter.cpp b/libs/openal-soft/core/uhjfilter.cpp index 5b40a981..c432acc4 100644 --- a/libs/openal-soft/core/uhjfilter.cpp +++ b/libs/openal-soft/core/uhjfilter.cpp @@ -4,23 +4,27 @@ #include "uhjfilter.h" #include -#include +#include +#include +#include #include #include "alcomplex.h" -#include "alnumeric.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "core/bufferline.h" #include "opthelpers.h" #include "pffft.h" #include "phase_shifter.h" #include "vector.h" -UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; -UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; - - namespace { +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } + /* Convolution is implemented using a segmented overlap-add method. The filter * response is broken up into multiple segments of 128 samples, and each * segment has an FFT applied with a 256-sample buffer (the latter half left @@ -58,55 +62,48 @@ struct SegmentedFilter { SegmentedFilter() : mFft{sFftLength, PFFFT_REAL} { - using complex_d = std::complex; - constexpr size_t fft_size{N}; - constexpr size_t half_size{fft_size / 2}; + static constexpr size_t fft_size{N}; - /* To set up the filter, we need to generate the desired response. - * Start with a pure delay that passes all frequencies through. + /* To set up the filter, we first need to generate the desired + * response (not reversed). */ - auto fftBuffer = std::vector(fft_size, complex_d{}); - fftBuffer[half_size] = 1.0; + auto tmpBuffer = std::vector(fft_size, 0.0); + for(std::size_t i{0};i < fft_size/2;++i) + { + const auto k = int{fft_size/2} - static_cast(i*2 + 1); - /* Convert to the frequency domain, shift the phase of each bin by +90 - * degrees, then convert back to the time domain. - * - * NOTE: The 0- and half-frequency are always real for a real signal. - * To maintain that and their phase (0 or pi), they're heavily - * attenuated instead of shifted like the others. - */ - forward_fft(al::span{fftBuffer}); - fftBuffer[0] *= std::numeric_limits::epsilon(); - for(size_t i{1};i < half_size;++i) - fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; - fftBuffer[half_size] *= std::numeric_limits::epsilon(); - for(size_t i{half_size+1};i < fft_size;++i) - fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); - inverse_fft(al::span{fftBuffer}); + const auto w = 2.0*al::numbers::pi/double{fft_size} * static_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 = al::numbers::pi * static_cast(k); + tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; + } /* The segments of the filter are converted back to the frequency * domain, each on their own (0 stuffed). */ - auto fftBuffer2 = std::vector(sFftLength); + using complex_d = std::complex; + auto fftBuffer = std::vector(sFftLength); auto fftTmp = al::vector(sFftLength); - float *filter{mFilterData.data()}; + auto filter = mFilterData.begin(); for(size_t s{0};s < sNumSegments;++s) { - for(size_t i{0};i < sSampleLength;++i) - fftBuffer2[i] = fftBuffer[sSampleLength*s + i].real() / double{fft_size}; - std::fill_n(fftBuffer2.data()+sSampleLength, sSampleLength, complex_d{}); - forward_fft(al::span{fftBuffer2}); + const auto tmpspan = al::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength); + auto iter = std::copy_n(tmpspan.cbegin(), tmpspan.size(), fftBuffer.begin()); + std::fill(iter, fftBuffer.end(), complex_d{}); + forward_fft(fftBuffer); /* Convert to zdomain data for PFFFT, scaled by the FFT length so * the iFFT result will be normalized. */ for(size_t i{0};i < sSampleLength;++i) { - fftTmp[i*2 + 0] = static_cast(fftBuffer2[i].real()) / float{sFftLength}; - fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer2[sSampleLength].real() - : fftBuffer2[i].imag()) / float{sFftLength}; + fftTmp[i*2 + 0] = static_cast(fftBuffer[i].real()) / float{sFftLength}; + fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer[sSampleLength].real() + : fftBuffer[i].imag()) / float{sFftLength}; } - mFft.zreorder(fftTmp.data(), filter, PFFFT_BACKWARD); + mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD); filter += sFftLength; } } @@ -133,11 +130,10 @@ constexpr std::array Filter2Coeff{{ 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }}; -} // namespace -void UhjAllPassFilter::processOne(const al::span coeffs, float x) +void processOne(UhjAllPassFilter &self, const al::span coeffs, float x) { - auto state = mState; + auto state = self.mState; for(size_t i{0};i < 4;++i) { const float y{x*coeffs[i] + state[i].z[0]}; @@ -145,13 +141,13 @@ void UhjAllPassFilter::processOne(const al::span coeffs, float x state[i].z[1] = y*coeffs[i] - x; x = y; } - mState = state; + self.mState = state; } -void UhjAllPassFilter::process(const al::span coeffs, - const al::span src, const bool updateState, float *RESTRICT dst) +void process(UhjAllPassFilter &self, const al::span coeffs, + const al::span src, const bool updateState, const al::span dst) { - auto state = mState; + auto state = self.mState; auto proc_sample = [&state,coeffs](float x) noexcept -> float { @@ -164,10 +160,11 @@ void UhjAllPassFilter::process(const al::span coeffs, } return x; }; - std::transform(src.begin(), src.end(), dst, proc_sample); - if(updateState) LIKELY mState = state; + std::transform(src.begin(), src.end(), dst.begin(), proc_sample); + if(updateState) LIKELY self.mState = state; } +} // namespace /* Encoding UHJ from B-Format is done as: * @@ -196,32 +193,39 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, static_assert(sNumSegments == Filter.sNumSegments); ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; - std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay); - std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay); - std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay); + std::copy_n(winput.begin(), SamplesToDo, mW.begin()+sFilterDelay); + std::copy_n(xinput.begin(), SamplesToDo, mX.begin()+sFilterDelay); + std::copy_n(yinput.begin(), SamplesToDo, mY.begin()+sFilterDelay); /* S = 0.9396926*W + 0.1855740*X */ - for(size_t i{0};i < SamplesToDo;++i) - mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i]; + std::transform(mW.begin(), mW.begin()+SamplesToDo, mX.begin(), mS.begin(), + [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ + auto dstore = mD.begin(); size_t curseg{mCurrentSegment}; for(size_t base{0};base < SamplesToDo;) { const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; + auto wseg = winput.subspan(base, todo); + auto xseg = xinput.subspan(base, todo); + /* Some Clang versions don't like calling subspan on an rvalue here. */ + const auto wxio_ = al::span{mWXInOut}; + auto wxio = wxio_.subspan(mFifoPos, todo); /* Copy out the samples that were previously processed by the FFT. */ - std::copy_n(mWXInOut.begin()+mFifoPos, todo, mD.begin()+base); + dstore = std::copy_n(wxio.begin(), todo, dstore); /* Transform the non-delayed input and store in the front half of the * filter input. */ - std::transform(winput+base, winput+base+todo, xinput+base, mWXInOut.begin()+mFifoPos, + std::transform(wseg.begin(), wseg.end(), xseg.begin(), wxio.begin(), [](const float w, const float x) noexcept -> float { return -0.3420201f*w + 0.5098604f*x; }); @@ -235,27 +239,30 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, /* Copy the new input to the next history segment, clearing the back * half of the segment, and convert to the frequency domain. */ - float *input{mWXHistory.data() + curseg*sFftLength}; + auto input = mWXHistory.begin() + curseg*sFftLength; std::copy_n(mWXInOut.begin(), sSegmentSize, input); std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f); - Filter.mFft.transform(input, input, mWorkData.data(), PFFFT_FORWARD); + Filter.mFft.transform(al::to_address(input), al::to_address(input), mWorkData.data(), + PFFFT_FORWARD); /* Convolve each input segment with its IR filter counterpart (aligned * in time, from newest to oldest). */ mFftBuffer.fill(0.0f); - const float *filter{Filter.mFilterData.data()}; + auto filter = Filter.mFilterData.begin(); for(size_t s{curseg};s < sNumSegments;++s) { - Filter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.data()); + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); input += sFftLength; filter += sFftLength; } - input = mWXHistory.data(); + input = mWXHistory.begin(); for(size_t s{0};s < curseg;++s) { - Filter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.data()); + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); input += sFftLength; filter += sFftLength; } @@ -266,10 +273,9 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(), PFFFT_BACKWARD); - for(size_t i{0};i < sSegmentSize;++i) - mWXInOut[i] = mFftBuffer[i] + mWXInOut[sSegmentSize+i]; - for(size_t i{0};i < sSegmentSize;++i) - mWXInOut[sSegmentSize+i] = mFftBuffer[sSegmentSize+i]; + std::transform(mFftBuffer.begin(), mFftBuffer.begin()+sSegmentSize, + mWXInOut.begin()+sSegmentSize, mWXInOut.begin(), std::plus{}); + std::copy_n(mFftBuffer.begin()+sSegmentSize, sSegmentSize, mWXInOut.begin()+sSegmentSize); /* Shift the input history. */ curseg = curseg ? (curseg-1) : (sNumSegments-1); @@ -277,8 +283,8 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, mCurrentSegment = curseg; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mD[i] + 0.6554516f*mY[i]; + std::transform(mD.begin(), mD.begin()+SamplesToDo, mY.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Copy the future samples to the front for next time. */ std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin()); @@ -286,35 +292,35 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin()); /* Apply a delay to the existing output to align with the input delay. */ - auto *delayBuffer = mDirectDelay.data(); + auto delayBuffer = mDirectDelay.begin(); for(float *buffer : {LeftOut, RightOut}) { - float *distbuf{al::assume_aligned<16>(delayBuffer->data())}; + const auto distbuf = assume_aligned_span<16>(al::span{*delayBuffer}); ++delayBuffer; - float *inout{al::assume_aligned<16>(buffer)}; - auto inout_end = inout + SamplesToDo; + const auto inout = al::span{al::assume_aligned<16>(buffer), SamplesToDo}; if(SamplesToDo >= sFilterDelay) { - auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end); - std::swap_ranges(inout, delay_end, distbuf); + auto delay_end = std::rotate(inout.begin(), inout.end() - sFilterDelay, inout.end()); + std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, distbuf); - std::rotate(distbuf, delay_start, distbuf + sFilterDelay); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); + std::rotate(distbuf.begin(), delay_start, distbuf.begin() + sFilterDelay); } } /* Combine the direct signal with the produced output. */ /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) left[i] += (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) right[i] += (mS[i] - mD[i]) * 0.5f; } @@ -337,47 +343,49 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; /* S = 0.9396926*W + 0.1855740*X */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); - mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1); + process(mFilter1WX, Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, + al::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); - mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data()); + process(mFilter2WX, Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ - mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1); + process(mFilter1Y, Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[SamplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mWX[i] + 0.6554516f*mD[i]; + std::transform(mWX.begin(), mWX.begin()+SamplesToDo, mD.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Apply the base filter to the existing output to align with the processed * signal. */ - mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1); + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + process(mFilter1Direct[0], Filter1Coeff, left, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo]; /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + for(size_t i{0};i < SamplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; - mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1); + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + process(mFilter1Direct[1], Filter1Coeff, right, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo]; /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + for(size_t i{0};i < SamplesToDo;++i) right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } @@ -404,28 +412,26 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; - const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; + const auto t = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); /* T */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mT[i] = t[i]; + std::copy(t.begin(), t.end(), mT.begin()); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); @@ -433,21 +439,22 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ for(size_t i{0};i < samplesToDo;++i) @@ -455,10 +462,10 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*zoutput[i]; + std::transform(zoutput.begin(), zoutput.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } } @@ -468,65 +475,65 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ - std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput, mTemp.begin(), + std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(), + mTemp.begin(), [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); - if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]); - mFilter2DT.process(Filter2Coeff, {mTemp.data()+1, samplesToDo}, updateState, xoutput); + if(mFirstRun) processOne(mFilter2DT, Filter2Coeff, mTemp[0]); + process(mFilter2DT, Filter2Coeff, al::span{mTemp}.subspan(1, samplesToDo), updateState, + xoutput); /* Apply filter1 to S and store in mTemp. */ - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()); + process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mTemp[i] + 0.197484f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mTemp[i] - xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput, + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); - mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()); + process(mFilter1DT, Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); /* Precompute j*S and store in youtput. */ - if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); - mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput); + if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); + process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = mTemp[i] + 0.186633f*youtput[i]; - + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float dt, const float js) noexcept { return dt + 0.186633f*js; }); if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Apply filter1 to Q and store in mTemp. */ - mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()); + process(mFilter1Q, Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*mTemp[i]; + std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } mFirstRun = false; @@ -538,9 +545,9 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT * 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 + * 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. @@ -554,13 +561,13 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -569,53 +576,60 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } - for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wtarget; + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j*D and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); - /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i]; - /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i]; + /* W = 0.6098637*S + 0.6896511*j*w*D */ + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); + /* X = 0.8624776*S - 0.7626955*j*w*D */ + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); - /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i]; + /* Y = 1.6822415*w*D + 0.2156194*j*S */ + std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, @@ -624,13 +638,13 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -639,53 +653,61 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept + { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } - for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wtarget; + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Apply filter1 to S and store in mTemp. */ - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()); + process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ - if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]); - mFilter2D.process(Filter2Coeff, {mD.data()+1, samplesToDo}, updateState, xoutput); + if(mFirstRun) processOne(mFilter2D, Filter2Coeff, mD[0]); + process(mFilter2D, Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); - /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mTemp[i] - 0.6896511f*xoutput[i]; - /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i]; + /* W = 0.6098637*S + 0.6896511*j*w*D */ + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); + /* X = 0.8624776*S - 0.7626955*j*w*D */ + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ - if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); - mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput); + if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); + process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ - mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()); + process(mFilter1D, Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); - /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i]; + /* Y = 1.6822415*w*D + 0.2156194*j*S */ + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); mFirstRun = false; } diff --git a/libs/openal-soft/core/uhjfilter.h b/libs/openal-soft/core/uhjfilter.h index eb822ebf..34ea1dfa 100644 --- a/libs/openal-soft/core/uhjfilter.h +++ b/libs/openal-soft/core/uhjfilter.h @@ -2,11 +2,12 @@ #define CORE_UHJFILTER_H #include +#include #include -#include "almalloc.h" #include "alspan.h" #include "bufferline.h" +#include "opthelpers.h" inline constexpr std::size_t UhjLength256{256}; @@ -19,8 +20,8 @@ enum class UhjQualityType : std::uint8_t { Default = IIR }; -extern UhjQualityType UhjDecodeQuality; -extern UhjQualityType UhjEncodeQuality; +inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; +inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; struct UhjAllPassFilter { @@ -29,16 +30,18 @@ struct UhjAllPassFilter { std::array z{}; }; std::array mState; - - void processOne(const al::span coeffs, float x); - void process(const al::span coeffs, const al::span src, - const bool update, float *RESTRICT dst); }; -struct UhjEncoderBase { +struct SIMDALIGN UhjEncoderBase { + UhjEncoderBase() = default; + UhjEncoderBase(const UhjEncoderBase&) = delete; + UhjEncoderBase(UhjEncoderBase&&) = delete; virtual ~UhjEncoderBase() = default; + void operator=(const UhjEncoderBase&) = delete; + void operator=(UhjEncoderBase&&) = delete; + virtual std::size_t getDelay() noexcept = 0; /** @@ -82,7 +85,7 @@ struct UhjEncoder final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const std::size_t SamplesToDo) override; + const std::size_t SamplesToDo) final; }; struct UhjEncoderIIR final : public UhjEncoderBase { @@ -110,19 +113,25 @@ struct UhjEncoderIIR final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const std::size_t SamplesToDo) override; + const std::size_t SamplesToDo) final; }; -struct DecoderBase { +struct SIMDALIGN DecoderBase { static constexpr std::size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ static constexpr float sWLFScale{0.661f}; static constexpr float sXYLFScale{1.293f}; + DecoderBase() = default; + DecoderBase(const DecoderBase&) = delete; + DecoderBase(DecoderBase&&) = delete; virtual ~DecoderBase() = default; + void operator=(const DecoderBase&) = delete; + void operator=(DecoderBase&&) = delete; + virtual void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) = 0; @@ -156,7 +165,7 @@ struct UhjDecoder final : public DecoderBase { * B-Format decoder, as it needs different shelf filters. */ void decode(const al::span samples, const std::size_t samplesToDo, - const bool updateState) override; + const bool updateState) final; }; struct UhjDecoderIIR final : public DecoderBase { @@ -180,7 +189,7 @@ struct UhjDecoderIIR final : public DecoderBase { UhjAllPassFilter mFilter1Q; void decode(const al::span samples, const std::size_t samplesToDo, - const bool updateState) override; + const bool updateState) final; }; template @@ -204,7 +213,7 @@ struct UhjStereoDecoder final : public DecoderBase { * channels, and the third left empty. */ void decode(const al::span samples, const std::size_t samplesToDo, - const bool updateState) override; + const bool updateState) final; }; struct UhjStereoDecoderIIR final : public DecoderBase { @@ -223,7 +232,7 @@ struct UhjStereoDecoderIIR final : public DecoderBase { UhjAllPassFilter mFilter2S; void decode(const al::span samples, const std::size_t samplesToDo, - const bool updateState) override; + const bool updateState) final; }; #endif /* CORE_UHJFILTER_H */ diff --git a/libs/openal-soft/core/uiddefs.cpp b/libs/openal-soft/core/uiddefs.cpp index 9471bba5..e52a9ae3 100644 --- a/libs/openal-soft/core/uiddefs.cpp +++ b/libs/openal-soft/core/uiddefs.cpp @@ -1,17 +1,13 @@ #include "config.h" - +#include "config_backends.h" #ifndef AL_NO_UID_DEFS -#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) +#if defined(HAVE_GUIDDEF_H) #define INITGUID #include -#ifdef HAVE_GUIDDEF_H #include -#else -#include -#endif 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); @@ -20,7 +16,7 @@ DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x0 DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); -#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP) +#if HAVE_WASAPI && !ALSOFT_UWP #include #include #include diff --git a/libs/openal-soft/core/voice.cpp b/libs/openal-soft/core/voice.cpp index a2251a7f..31214f0e 100644 --- a/libs/openal-soft/core/voice.cpp +++ b/libs/openal-soft/core/voice.cpp @@ -1,5 +1,6 @@ #include "config.h" +#include "config_simd.h" #include "voice.h" @@ -42,10 +43,10 @@ #include "voice_change.h" struct CTag; -#ifdef HAVE_SSE +#if HAVE_SSE struct SSETag; #endif -#ifdef HAVE_NEON +#if HAVE_NEON struct NEONTag; #endif @@ -57,30 +58,29 @@ static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for Buffer static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -Resampler ResamplerDefault{Resampler::Cubic}; - namespace { using uint = unsigned int; using namespace std::chrono; using namespace std::string_view_literals; -using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); -using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, - const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize); +using HrtfMixerFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, + const size_t SamplesToDo); +using HrtfMixerBlendFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo); HrtfMixerFunc MixHrtfSamples{MixHrtf_}; HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; inline MixerOutFunc SelectMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif @@ -89,11 +89,11 @@ inline MixerOutFunc SelectMixer() inline MixerOneFunc SelectMixerOne() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif @@ -102,11 +102,11 @@ inline MixerOneFunc SelectMixerOne() inline HrtfMixerFunc SelectHrtfMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtf_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtf_; #endif @@ -115,11 +115,11 @@ inline HrtfMixerFunc SelectHrtfMixer() inline HrtfMixerBlendFunc SelectHrtfBlendMixer() { -#ifdef HAVE_NEON +#if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtfBlend_; #endif -#ifdef HAVE_SSE +#if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtfBlend_; #endif @@ -140,31 +140,40 @@ void Voice::InitMixer(std::optional resopt) ResamplerEntry{"none"sv, Resampler::Point}, ResamplerEntry{"point"sv, Resampler::Point}, ResamplerEntry{"linear"sv, Resampler::Linear}, - ResamplerEntry{"cubic"sv, Resampler::Cubic}, + 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}, }; std::string_view resampler{*resopt}; - if(al::case_compare(resampler, "bsinc"sv) == 0) + + if (al::case_compare(resampler, "cubic"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str()); - resampler = "bsinc12"sv; + 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 \"%s\" is deprecated, using cubic\n", resopt->c_str()); - resampler = "cubic"sv; + WARN("Resampler option \"{}\" is deprecated, using gaussian", *resopt); + resampler = "gaussian"sv; + } + else if(al::case_compare(resampler, "bsinc"sv) == 0) + { + WARN("Resampler option \"{}\" is deprecated, using bsinc12", *resopt); + resampler = "bsinc12"sv; } auto iter = std::find_if(ResamplerList.begin(), ResamplerList.end(), [resampler](const ResamplerEntry &entry) -> bool { return al::case_compare(resampler, entry.name) == 0; }); if(iter == ResamplerList.end()) - ERR("Invalid resampler: %s\n", resopt->c_str()); + ERR("Invalid resampler: {}", *resopt); else ResamplerDefault = iter->resampler; } @@ -198,7 +207,7 @@ constexpr std::array IMA4Codeword{{ }}; /* IMA4 ADPCM Step index adjust decode table */ -constexpr std::arrayIMA4Index_adjust{{ +constexpr std::array IMA4Index_adjust{{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 }}; @@ -225,9 +234,9 @@ void SendSourceStoppedEvent(ContextBase *context, uint id) { RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len < 1) return; + if(evt_vec[0].len < 1) return; - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; evt.mState = AsyncSrcState::Stop; @@ -269,18 +278,17 @@ inline void LoadSamples(const al::span dstSamples, const al::span; using SampleType = typename TypeTraits::Type; - static constexpr size_t sampleSize{sizeof(SampleType)}; assert(srcChan < srcStep); auto converter = TypeTraits{}; al::span src{reinterpret_cast(srcData.data()), - srcData.size()/sampleSize}; - auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep); - std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter] + srcData.size()/sizeof(SampleType)}; + auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep + srcChan); + dstSamples.front() = converter(*ssrc); + std::generate(dstSamples.begin()+1, dstSamples.end(), [&ssrc,srcStep,converter] { - auto ret = converter(ssrc[srcChan]); ssrc += ptrdiff_t(srcStep); - return ret; + return converter(*ssrc); }); } @@ -302,46 +310,53 @@ inline void LoadSamples(al::span dstSamples, al::span(sample) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(prevSample) / 32768.0f; + if(++dst == dstSamples.end()) return; } else --skip; - auto decode_sample = [&sample,&index](const uint nibble) - { - sample += IMA4Codeword[nibble] * IMAStep_size[static_cast(index)] / 8; - sample = std::clamp(sample, -32768, 32767); - - index += IMA4Index_adjust[nibble]; - index = std::clamp(index, 0, MaxStepIndex); - - return sample; - }; - /* The rest of the block is arranged as a series of nibbles, contained * in 4 *bytes* per channel interleaved. So every 8 nibbles we need to * skip 4 bytes per channel to get the next nibbles for this channel. - * - * First, decode the samples that we need to skip in the block (will + */ + auto decode_nibble = [&prevSample,&prevIndex,srcStep,nibbleData](const size_t nibbleOffset) + noexcept -> int + { + static constexpr auto NibbleMask = std::byte{0xf}; + const auto byteShift = (nibbleOffset&1) * 4; + const auto wordOffset = (nibbleOffset>>1) & ~3_uz; + const auto byteOffset = wordOffset*srcStep + ((nibbleOffset>>1)&3); + + const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask); + + prevSample += IMA4Codeword[nibble] * IMAStep_size[static_cast(prevIndex)] / 8; + prevSample = std::clamp(prevSample, -32768, 32767); + + prevIndex += IMA4Index_adjust[nibble]; + prevIndex = std::clamp(prevIndex, 0, MaxStepIndex); + + return prevSample; + }; + + /* 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. */ @@ -349,29 +364,22 @@ inline void LoadSamples(al::span dstSamples, al::span>1) & ~3_uz}; - const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + std::ignore = decode_nibble(nibbleOffset); ++nibbleOffset; - - std::ignore = decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u); } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ - const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; - std::generate_n(dstSamples.begin(), todo, [&] + const auto todo = std::min(samplesPerBlock - startOffset, + size_t(std::distance(dst, dstSamples.end()))); + dst = std::generate_n(dst, todo, [&] { - const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; - const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + const auto sample = decode_nibble(nibbleOffset); ++nibbleOffset; - const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)}; - return static_cast(result) / 32768.0f; + return static_cast(sample) / 32768.0f; }); - dstSamples = dstSamples.subspan(todo); } } @@ -388,30 +396,27 @@ inline void LoadSamples(al::span dstSamples, al::span sampleHistory{}; - sampleHistory[0] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); - input += ptrdiff_t(srcStep*2); - sampleHistory[1] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); - input += ptrdiff_t(srcStep*2); + auto sampleHistory = std::array{ + int(src[3*srcStep + 2*srcChan + 0]) | (int(src[3*srcStep + 2*srcChan + 1])<<8), + int(src[5*srcStep + 2*srcChan + 0]) | (int(src[5*srcStep + 2*srcChan + 1])<<8)}; + const auto nibbleData = src.subspan(7*srcStep); + src = src.subspan(blockBytes); - const al::span coeffs{MSADPCMAdaptionCoeff[blockpred]}; - delta = (delta^0x8000) - 32768; + const auto coeffs = al::span{MSADPCMAdaptionCoeff[blockpred]}; + scale = (scale^0x8000) - 32768; sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; @@ -420,66 +425,66 @@ inline void LoadSamples(al::span dstSamples, al::span(sampleHistory[1]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; - dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(sampleHistory[1]) / 32768.0f; + if(++dst == dstSamples.end()) return; + *dst = static_cast(sampleHistory[0]) / 32768.0f; + if(++dst == dstSamples.end()) return; } else if(skip == 1) { --skip; - dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; - dstSamples = dstSamples.subspan<1>(); - if(dstSamples.empty()) return; + *dst = static_cast(sampleHistory[0]) / 32768.0f; + if(++dst == dstSamples.end()) return; } else skip -= 2; - auto decode_sample = [&sampleHistory,&delta,coeffs](const int nibble) + /* The rest of the block is a series of nibbles, interleaved per- + * channel. + */ + auto decode_nibble = [&sampleHistory,&scale,coeffs,nibbleData](const size_t nibbleOffset) + noexcept -> int { - int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; - pred += ((nibble^0x08) - 0x08) * delta; - pred = std::clamp(pred, -32768, 32767); + static constexpr auto NibbleMask = std::byte{0xf}; + const auto byteOffset = nibbleOffset>>1; + const auto byteShift = ((nibbleOffset&1)^1) * 4; + + const auto nibble = al::to_underlying((nibbleData[byteOffset]>>byteShift)&NibbleMask); + + const auto pred = ((nibble^0x08) - 0x08) * scale; + const auto diff = (sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256; + const auto sample = std::clamp(pred + diff, -32768, 32767); sampleHistory[1] = sampleHistory[0]; - sampleHistory[0] = pred; + sampleHistory[0] = sample; - delta = (MSADPCMAdaption[static_cast(nibble)] * delta) / 256; - delta = std::max(16, delta); + scale = MSADPCMAdaption[nibble] * scale / 256; + scale = std::max(16, scale); - return pred; + return sample; }; - /* The rest of the block is a series of nibbles, interleaved per- - * channel. First, skip samples. - */ + /* First, skip samples. */ const size_t startOffset{skip + 2}; size_t nibbleOffset{srcChan}; for(;skip;--skip) { - const size_t byteOffset{nibbleOffset>>1}; - const size_t byteShift{((nibbleOffset&1)^1) * 4}; + std::ignore = decode_nibble(nibbleOffset); nibbleOffset += srcStep; - - std::ignore = decode_sample(int(input[byteOffset]>>byteShift) & 15); } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ - const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; - std::generate_n(dstSamples.begin(), todo, [&] + const auto todo = std::min(samplesPerBlock - startOffset, + size_t(std::distance(dst, dstSamples.end()))); + dst = std::generate_n(dst, todo, [&] { - const size_t byteOffset{nibbleOffset>>1}; - const size_t byteShift{((nibbleOffset&1)^1) * 4}; + const auto sample = decode_nibble(nibbleOffset); nibbleOffset += srcStep; - const int sample{decode_sample(int(input[byteOffset]>>byteShift) & 15)}; return static_cast(sample) / 32768.0f; }); - dstSamples = dstSamples.subspan(todo); } } @@ -607,7 +612,7 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, void DoHrtfMix(const al::span samples, DirectParams &parms, const float TargetGain, - const size_t Counter, uint OutPos, const bool IsPlaying, DeviceBase *Device) + const size_t Counter, size_t OutPos, const bool IsPlaying, DeviceBase *Device) { const uint IrSize{Device->mIrSize}; const auto HrtfSamples = al::span{Device->ExtraSampleData}; @@ -619,8 +624,10 @@ void DoHrtfMix(const al::span samples, DirectParams &parms, const f std::copy_n(samples.begin(), samples.size(), src_iter); /* Copy the last used samples back into the history buffer for later. */ if(IsPlaying) LIKELY - std::copy_n(HrtfSamples.begin() + ptrdiff_t(samples.size()), parms.Hrtf.History.size(), - parms.Hrtf.History.begin()); + { + const auto endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size()); + std::copy_n(endsamples.cbegin(), endsamples.size(), parms.Hrtf.History.begin()); + } /* If fading and this is the first mixing pass, fade between the IRs. */ size_t fademix{0}; @@ -645,8 +652,8 @@ void DoHrtfMix(const al::span samples, DirectParams &parms, const f parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, 0.0f, gain / static_cast(fademix)}; - MixHrtfBlendSamples(HrtfSamples.data(), AccumSamples.data()+OutPos, IrSize, - &parms.Hrtf.Old, &hrtfparams, 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; @@ -673,8 +680,8 @@ void DoHrtfMix(const al::span samples, DirectParams &parms, const f parms.Hrtf.Target.Delay, parms.Hrtf.Old.Gain, (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; - MixHrtfSamples(HrtfSamples.data()+fademix, AccumSamples.data()+OutPos, IrSize, &hrtfparams, - todo); + MixHrtfSamples(HrtfSamples.subspan(fademix), AccumSamples.subspan(OutPos), IrSize, + &hrtfparams, todo); /* Store the now-current gain for next time. */ parms.Hrtf.Old.Gain = gain; @@ -682,32 +689,31 @@ void DoHrtfMix(const al::span samples, DirectParams &parms, const f } void DoNfcMix(const al::span samples, al::span OutBuffer, - DirectParams &parms, const float *TargetGains, const uint Counter, const uint OutPos, - DeviceBase *Device) + DirectParams &parms, const al::span OutGains, + const uint Counter, const uint OutPos, DeviceBase *Device) { using FilterProc = void (NfcFilter::*)(const al::span, const al::span); static constexpr std::array NfcProcess{{ nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}}; - auto CurrentGains = parms.Gains.Current.begin(); - MixSamples(samples, OutBuffer.first<1>(), al::to_address(CurrentGains), TargetGains, Counter, - OutPos); - OutBuffer = OutBuffer.subspan<1>(); - ++CurrentGains; - ++TargetGains; + MixSamples(samples, al::span{OutBuffer[0]}.subspan(OutPos), parms.Gains.Current[0], + OutGains[0], Counter); + OutBuffer = OutBuffer.subspan(1); + auto CurrentGains = al::span{parms.Gains.Current}.subspan(1); + auto TargetGains = OutGains.subspan(1); - const auto nfcsamples = al::span{Device->ExtraSampleData.begin(), samples.size()}; + const auto nfcsamples = al::span{Device->ExtraSampleData}.first(samples.size()); size_t order{1}; while(const size_t chancount{Device->NumChannelsPerOrder[order]}) { (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); - MixSamples(nfcsamples, OutBuffer.first(chancount), al::to_address(CurrentGains), - TargetGains, Counter, OutPos); - OutBuffer = OutBuffer.subspan(chancount); - CurrentGains += chancount; - TargetGains += chancount; + 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); } } @@ -770,17 +776,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* 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. - * - * Round the start position to a multiple of 4, which some mixers want. - * This makes the start time accurate to 4 samples. This could be made - * sample-accurate by forcing non-SIMD functions on the first run. */ - seconds::rep sampleOffset{duration_cast(diff * Device->Frequency).count()}; - sampleOffset = (sampleOffset+2) & ~seconds::rep{3}; - if(sampleOffset >= SamplesToDo) - return; - - OutPos = static_cast(sampleOffset); + OutPos = static_cast(round(diff * Device->mSampleRate).count()); + if(OutPos >= SamplesToDo) return; } /* Calculate the number of samples to mix, and the number of (resampled) @@ -792,8 +790,8 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Get a span of pointers to hold the floating point, deinterlaced, * resampled buffer data to be mixed. */ - std::array SamplePointers; - const al::span MixingSamples{SamplePointers.data(), mChans.size()}; + auto SamplePointers = std::array{}; + const auto MixingSamples = al::span{SamplePointers}.first(mChans.size()); { const uint channelStep{(samplesToLoad+3u)&~3u}; auto base = Device->mSampleData.end() - MixingSamples.size()*channelStep; @@ -851,7 +849,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi dataSize64 += ext + MaxResamplerEdge; if(dataSize64 <= srcSizeMax) - return std::make_pair(dstBufferSize, static_cast(dataSize64)); + return std::array{dstBufferSize, static_cast(dataSize64)}; /* If the source size got saturated, we can't fill the desired * dst size. Figure out how many dst samples we can fill. @@ -866,7 +864,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi */ dstBufferSize = static_cast(dataSize64) & ~3u; } - return std::make_pair(dstBufferSize, srcSizeMax); + return std::array{dstBufferSize, srcSizeMax}; }; const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes( samplesToLoad - samplesLoaded); @@ -896,17 +894,16 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi { const uint avail{std::min(srcBufferSize, MaxResamplerEdge)}; const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)}; + const auto srcbuf = resampleBuffer.first(tofill); /* When loading from a voice that ended prematurely, only take * the samples that get closest to 0 amplitude. This helps * certain sounds fade out better. */ - auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool - { return std::abs(lhs) < std::abs(rhs); }; - auto srciter = std::min_element(resampleBuffer.begin(), - resampleBuffer.begin()+ptrdiff_t(avail), abs_lt); + auto srciter = std::min_element(srcbuf.begin(), srcbuf.begin()+ptrdiff_t(avail), + [](const float l, const float r) { return std::abs(l) < std::abs(r); }); - std::fill(srciter+1, resampleBuffer.begin()+ptrdiff_t(tofill), *srciter); + std::fill(srciter+1, srcbuf.end(), *srciter); } else if(mFlags.test(VoiceIsStatic)) { @@ -962,7 +959,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi std::copy_n(resampleBuffer.cbegin(), dstBufferSize, MixingSamples[chan]+samplesLoaded); else - mResampler(&mResampleState, resampleBuffer.data(), fracPos, increment, + mResampler(&mResampleState, Device->mResampleData, fracPos, increment, {MixingSamples[chan]+samplesLoaded, dstBufferSize}); /* Store the last source samples used for next time. */ @@ -1066,13 +1063,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi } else { - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; if(mFlags.test(VoiceHasNfc)) DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device); else - MixSamples(samples, mDirect.Buffer, parms.Gains.Current.data(), TargetGains, - Counter, OutPos); + MixSamples(samples, mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } } @@ -1085,10 +1082,10 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, {*voiceSamples, samplesToMix}, mSend[send].FilterType); - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; - MixSamples(samples, mSend[send].Buffer, parms.Gains.Current.data(), TargetGains, - Counter, OutPos); + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; + MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } ++voiceSamples; @@ -1191,9 +1188,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if(evt_vec.first.len > 0) + if(evt_vec[0].len > 0) { - auto &evt = InitAsyncEvent(evt_vec.first.buf); + auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = SourceID; evt.mCount = buffers_done; ring->writeAdvance(1); @@ -1221,7 +1218,7 @@ void Voice::prepare(DeviceBase *device) : ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))}; if(num_channels > device->MixerChannelsMax) UNLIKELY { - ERR("Unexpected channel count: %u (limit: %zu, %s : %d)\n", num_channels, + ERR("Unexpected channel count: {} (limit: {}, {} : {})", num_channels, device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder); num_channels = device->MixerChannelsMax; } @@ -1296,7 +1293,7 @@ void Voice::prepare(DeviceBase *device) * Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is * identity, so don't mess with it). */ - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = 1.0f; @@ -1323,7 +1320,7 @@ void Voice::prepare(DeviceBase *device) const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; diff --git a/libs/openal-soft/core/voice.h b/libs/openal-soft/core/voice.h index 3d5385bb..bb7c6e24 100644 --- a/libs/openal-soft/core/voice.h +++ b/libs/openal-soft/core/voice.h @@ -10,7 +10,6 @@ #include #include -#include "almalloc.h" #include "alspan.h" #include "bufferline.h" #include "buffer_storage.h" @@ -20,6 +19,7 @@ #include "filters/splitter.h" #include "mixer/defs.h" #include "mixer/hrtfdefs.h" +#include "opthelpers.h" #include "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" @@ -102,7 +102,10 @@ struct VoiceBufferItem { uint mLoopStart{0u}; uint mLoopEnd{0u}; - al::span mSamples{}; + al::span mSamples; + +protected: + ~VoiceBufferItem() = default; }; @@ -180,7 +183,7 @@ enum : uint { VoiceFlagCount }; -struct Voice { +struct SIMDALIGN Voice { enum State { Stopped, Playing, @@ -233,9 +236,9 @@ struct Voice { ResamplerFunc mResampler{}; - InterpState mResampleState{}; + InterpState mResampleState; - std::bitset mFlags{}; + std::bitset mFlags; uint mNumCallbackBlocks{0}; uint mCallbackBlockBase{0}; @@ -277,6 +280,6 @@ struct Voice { static void InitMixer(std::optional resopt); }; -extern Resampler ResamplerDefault; +inline Resampler ResamplerDefault{Resampler::Spline}; #endif /* CORE_VOICE_H */ diff --git a/libs/openal-soft/docs/env-vars.txt b/libs/openal-soft/docs/env-vars.txt index 0c15cbe9..97e5882d 100644 --- a/libs/openal-soft/docs/env-vars.txt +++ b/libs/openal-soft/docs/env-vars.txt @@ -50,6 +50,10 @@ 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 @@ -78,6 +82,18 @@ 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), diff --git a/libs/openal-soft/examples/aldebug.cpp b/libs/openal-soft/examples/aldebug.cpp new file mode 100644 index 00000000..7669634f --- /dev/null +++ b/libs/openal-soft/examples/aldebug.cpp @@ -0,0 +1,311 @@ +/* + * OpenAL Debug Context Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for using the debug extension. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alnumeric.h" +#include "alspan.h" +#include "fmt/core.h" + +#include "win_main_utf8.h" + +namespace { + +using namespace std::string_view_literals; + +struct DeviceCloser { + void operator()(ALCdevice *device) const noexcept { alcCloseDevice(device); } +}; +using DevicePtr = std::unique_ptr; + +struct ContextDestroyer { + void operator()(ALCcontext *context) const noexcept { alcDestroyContext(context); } +}; +using ContextPtr = std::unique_ptr; + + +constexpr auto GetDebugSourceName(ALenum source) noexcept -> std::string_view +{ + switch(source) + { + case AL_DEBUG_SOURCE_API_EXT: return "API"sv; + case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return "Audio System"sv; + case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return "Third Party"sv; + case AL_DEBUG_SOURCE_APPLICATION_EXT: return "Application"sv; + case AL_DEBUG_SOURCE_OTHER_EXT: return "Other"sv; + } + return ""sv; +} + +constexpr auto GetDebugTypeName(ALenum type) noexcept -> std::string_view +{ + switch(type) + { + case AL_DEBUG_TYPE_ERROR_EXT: return "Error"sv; + case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return "Deprecated Behavior"sv; + case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return "Undefined Behavior"sv; + case AL_DEBUG_TYPE_PORTABILITY_EXT: return "Portability"sv; + case AL_DEBUG_TYPE_PERFORMANCE_EXT: return "Performance"sv; + case AL_DEBUG_TYPE_MARKER_EXT: return "Marker"sv; + case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return "Push Group"sv; + case AL_DEBUG_TYPE_POP_GROUP_EXT: return "Pop Group"sv; + case AL_DEBUG_TYPE_OTHER_EXT: return "Other"sv; + } + return ""sv; +} + +constexpr auto GetDebugSeverityName(ALenum severity) noexcept -> std::string_view +{ + switch(severity) + { + case AL_DEBUG_SEVERITY_HIGH_EXT: return "High"sv; + case AL_DEBUG_SEVERITY_MEDIUM_EXT: return "Medium"sv; + case AL_DEBUG_SEVERITY_LOW_EXT: return "Low"sv; + case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return "Notification"sv; + } + return ""sv; +} + +auto alDebugMessageCallbackEXT = LPALDEBUGMESSAGECALLBACKEXT{}; +auto alDebugMessageInsertEXT = LPALDEBUGMESSAGEINSERTEXT{}; +auto alDebugMessageControlEXT = LPALDEBUGMESSAGECONTROLEXT{}; +auto alPushDebugGroupEXT = LPALPUSHDEBUGGROUPEXT{}; +auto alPopDebugGroupEXT = LPALPOPDEBUGGROUPEXT{}; +auto alGetDebugMessageLogEXT = LPALGETDEBUGMESSAGELOGEXT{}; +auto alObjectLabelEXT = LPALOBJECTLABELEXT{}; +auto alGetObjectLabelEXT = LPALGETOBJECTLABELEXT{}; +auto alGetPointerEXT = LPALGETPOINTEREXT{}; +auto alGetPointervEXT = LPALGETPOINTERVEXT{}; + + +int main(al::span args) +{ + /* Print out usage if -h was specified */ + if(args.size() > 1 && (args[1] == "-h" || args[1] == "--help")) + { + fmt::println(stderr, "Usage: {} [-device ] [-nodebug]", args[0]); + return 1; + } + + /* Initialize OpenAL. */ + args = args.subspan(1); + + auto device = DevicePtr{}; + if(args.size() > 1 && args[0] == "-device") + { + device = DevicePtr{alcOpenDevice(std::string{args[1]}.c_str())}; + if(!device) + fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); + args = args.subspan(2); + } + if(!device) + device = DevicePtr{alcOpenDevice(nullptr)}; + if(!device) + { + fmt::println(stderr, "Could not open a device!"); + return 1; + } + + if(!alcIsExtensionPresent(device.get(), "ALC_EXT_debug")) + { + fmt::println(stderr, "ALC_EXT_debug not supported on device"); + return 1; + } + + /* Load the Debug API functions we're using. */ +#define LOAD_PROC(N) N = reinterpret_cast(alcGetProcAddress(device.get(), #N)) + LOAD_PROC(alDebugMessageCallbackEXT); + LOAD_PROC(alDebugMessageInsertEXT); + LOAD_PROC(alDebugMessageControlEXT); + LOAD_PROC(alPushDebugGroupEXT); + LOAD_PROC(alPopDebugGroupEXT); + LOAD_PROC(alGetDebugMessageLogEXT); + LOAD_PROC(alObjectLabelEXT); + LOAD_PROC(alGetObjectLabelEXT); + LOAD_PROC(alGetPointerEXT); + LOAD_PROC(alGetPointervEXT); +#undef LOAD_PROC + + /* Create a debug context and set it as current. If -nodebug was specified, + * create a non-debug context (to see how debug messages react). + */ + auto flags = ALCint{ALC_CONTEXT_DEBUG_BIT_EXT}; + if(!args.empty() && args[0] == "-nodebug") + flags &= ~ALC_CONTEXT_DEBUG_BIT_EXT; + + const auto attribs = std::array{{ + ALC_CONTEXT_FLAGS_EXT, flags, + 0 /* end-of-list */ + }}; + auto context = ContextPtr{alcCreateContext(device.get(), attribs.data())}; + if(!context || alcMakeContextCurrent(context.get()) == ALC_FALSE) + { + fmt::println(stderr, "Could not create and set a context!"); + return 1; + } + + /* Enable low-severity debug messages, which are disabled by default. */ + alDebugMessageControlEXT(AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, AL_DEBUG_SEVERITY_LOW_EXT, 0, + nullptr, AL_TRUE); + + fmt::println("Context flags: {:#010x}", as_unsigned(alGetInteger(AL_CONTEXT_FLAGS_EXT))); + + /* A debug context has debug output enabled by default. But in case this + * isn't a debug context, explicitly enable it (probably won't get much, if + * anything, in that case). + */ + fmt::println("Default debug state is: {}", + alIsEnabled(AL_DEBUG_OUTPUT_EXT) ? "enabled"sv : "disabled"sv); + alEnable(AL_DEBUG_OUTPUT_EXT); + + /* The max debug message length property will allow us to define message + * storage of sufficient length. This includes space for the null + * terminator. + */ + const auto maxloglength = alGetInteger(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT); + fmt::println("Max debug message length: {}", maxloglength); + + fmt::println(""); + + /* Doppler Velocity is deprecated since AL 1.1, so this should generate a + * deprecation debug message. We'll first handle debug messages through the + * message log, meaning we'll query for and read it afterward. + */ + fmt::println("Calling alDopplerVelocity(0.5f)..."); + alDopplerVelocity(0.5f); + + for(auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT);numlogs > 0;--numlogs) + { + auto message = std::vector(static_cast(maxloglength), '\0'); + auto source = ALenum{}; + auto type = ALenum{}; + auto id = ALuint{}; + auto severity = ALenum{}; + auto msglength = ALsizei{}; + + /* Getting the message removes it from the log. */ + const auto read = alGetDebugMessageLogEXT(1, maxloglength, &source, &type, &id, &severity, + &msglength, message.data()); + if(read != 1) + { + fmt::println(stderr, "Read {} debug messages, expected to read 1", read); + break; + } + + /* The message lengths returned by alGetDebugMessageLogEXT include the + * null terminator, so subtract one for the string_view length. If we + * read more than one message at a time, the length could be used as + * the offset to the next message. + */ + const auto msgstr = std::string_view{message.data(), + static_cast(msglength ? msglength-1 : 0)}; + fmt::println("Got message from log:\n" + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), msgstr); + } + fmt::println(""); + + /* Now set up a callback function. This lets us print the debug messages as + * they happen without having to explicitly query and get them. + */ + static constexpr auto debug_callback = [](ALenum source, ALenum type, ALuint id, + ALenum severity, ALsizei length, const ALchar *message, void *userParam [[maybe_unused]]) + noexcept -> void + { + /* The message length provided to the callback does not include the + * null terminator. + */ + const auto msgstr = std::string_view{message, static_cast(length)}; + fmt::println("Got message from callback:\n" + " Source: {}\n" + " Type: {}\n" + " ID: {}\n" + " Severity: {}\n" + " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), msgstr); + }; + alDebugMessageCallbackEXT(debug_callback, nullptr); + + if(const auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT)) + fmt::println(stderr, "{} left over logged message{}!", numlogs, (numlogs==1)?"":"s"); + + /* This should also generate a deprecation debug message, which will now go + * through the callback. + */ + fmt::println("Calling alGetInteger(AL_DOPPLER_VELOCITY)..."); + auto dv [[maybe_unused]] = alGetInteger(AL_DOPPLER_VELOCITY); + fmt::println(""); + + /* These functions are notoriously unreliable for their behavior, they will + * likely generate portability debug messages. + */ + fmt::println("Calling alcSuspendContext and alcProcessContext..."); + alcSuspendContext(context.get()); + alcProcessContext(context.get()); + fputs("\n", stdout); + + fmt::println("Pushing a debug group, making some invalid calls, and popping the debug group..."); + alPushDebugGroupEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, 0, -1, "Error test group"); + alSpeedOfSound(0.0f); + /* Can't set the label of the null buffer. */ + alObjectLabelEXT(AL_BUFFER, 0, -1, "The null buffer"); + alPopDebugGroupEXT(); + fmt::println(""); + + /* All done, insert a custom message and unset the callback. The context + * and device will clean themselves up. + */ + alDebugMessageInsertEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, AL_DEBUG_TYPE_MARKER_EXT, 0, + AL_DEBUG_SEVERITY_NOTIFICATION_EXT, -1, "End of run, cleaning up"); + alDebugMessageCallbackEXT(nullptr, nullptr); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/libs/openal-soft/examples/aldirect.cpp b/libs/openal-soft/examples/aldirect.cpp new file mode 100644 index 00000000..04de857f --- /dev/null +++ b/libs/openal-soft/examples/aldirect.cpp @@ -0,0 +1,472 @@ +/* + * OpenAL Direct Context Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for playing a sound buffer with the Direct API + * extension. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alspan.h" +#include "common/alhelpers.h" +#include "fmt/core.h" + +#include "win_main_utf8.h" + +namespace { + +/* On Windows when using Creative's router, we need to override the ALC + * functions and access the driver functions directly. This isn't needed when + * not using the router, or on other OSs. + */ +LPALCOPENDEVICE p_alcOpenDevice{alcOpenDevice}; +LPALCCLOSEDEVICE p_alcCloseDevice{alcCloseDevice}; +LPALCISEXTENSIONPRESENT p_alcIsExtensionPresent{alcIsExtensionPresent}; +LPALCCREATECONTEXT p_alcCreateContext{alcCreateContext}; +LPALCDESTROYCONTEXT p_alcDestroyContext{alcDestroyContext}; +LPALCGETPROCADDRESS p_alcGetProcAddress{alcGetProcAddress}; + + +LPALGETSTRINGDIRECT alGetStringDirect{}; +LPALGETERRORDIRECT alGetErrorDirect{}; +LPALISEXTENSIONPRESENTDIRECT alIsExtensionPresentDirect{}; + +LPALGENBUFFERSDIRECT alGenBuffersDirect{}; +LPALDELETEBUFFERSDIRECT alDeleteBuffersDirect{}; +LPALISBUFFERDIRECT alIsBufferDirect{}; +LPALBUFFERIDIRECT alBufferiDirect{}; +LPALBUFFERDATADIRECT alBufferDataDirect{}; + +LPALGENSOURCESDIRECT alGenSourcesDirect{}; +LPALDELETESOURCESDIRECT alDeleteSourcesDirect{}; +LPALSOURCEIDIRECT alSourceiDirect{}; +LPALGETSOURCEIDIRECT alGetSourceiDirect{}; +LPALGETSOURCEFDIRECT alGetSourcefDirect{}; +LPALSOURCEPLAYDIRECT alSourcePlayDirect{}; + + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + +enum class FormatType { + Int16, + Float, + IMA4, + MSADPCM +}; + +/* LoadBuffer loads the named audio file into an OpenAL buffer object, and + * returns the new buffer ID. + */ +ALuint LoadSound(ALCcontext *context, const std::string_view filename) +{ + /* Open the audio file and check that it's usable. */ + SF_INFO sfinfo{}; + SndFilePtr sndfile{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; + if(!sndfile) + { + fmt::println(stderr, "Could not open audio in {}: {}", filename, + sf_strerror(sndfile.get())); + return 0; + } + if(sfinfo.frames < 1) + { + fmt::println(stderr, "Bad sample count in {} ({})", filename, sfinfo.frames); + return 0; + } + + /* Detect a suitable format to load. Formats like Vorbis and Opus use float + * natively, so load as float to avoid clipping when possible. Formats + * larger than 16-bit can also use float to preserve a bit more precision. + */ + FormatType sample_format{FormatType::Int16}; + switch((sfinfo.format&SF_FORMAT_SUBMASK)) + { + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + case SF_FORMAT_VORBIS: + case SF_FORMAT_OPUS: + case SF_FORMAT_ALAC_20: + case SF_FORMAT_ALAC_24: + case SF_FORMAT_ALAC_32: + case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: + case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: + case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: + if(alIsExtensionPresentDirect(context, "AL_EXT_FLOAT32")) + sample_format = FormatType::Float; + break; + case SF_FORMAT_IMA_ADPCM: + /* ADPCM formats require setting a block alignment as specified in the + * file, which needs to be read from the wave 'fmt ' chunk manually + * since libsndfile doesn't provide it in a format-agnostic way. + */ + if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresentDirect(context, "AL_EXT_IMA4") + && alIsExtensionPresentDirect(context, "AL_SOFT_block_alignment")) + sample_format = FormatType::IMA4; + break; + case SF_FORMAT_MS_ADPCM: + if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresentDirect(context, "AL_SOFT_MSADPCM") + && alIsExtensionPresentDirect(context, "AL_SOFT_block_alignment")) + sample_format = FormatType::MSADPCM; + break; + } + + ALint byteblockalign{0}, splblockalign{0}; + if(sample_format == FormatType::IMA4 || sample_format == FormatType::MSADPCM) + { + /* For ADPCM, lookup the wave file's "fmt " chunk, which is a + * WAVEFORMATEX-based structure for the audio format. + */ + SF_CHUNK_INFO inf{"fmt ", 4, 0, nullptr}; + SF_CHUNK_ITERATOR *iter{sf_get_chunk_iterator(sndfile.get(), &inf)}; + + /* If there's an issue getting the chunk or block alignment, load as + * 16-bit and have libsndfile do the conversion. + */ + if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) + sample_format = FormatType::Int16; + else + { + auto fmtbuf = std::vector(inf.datalen, ALubyte{0}); + inf.data = fmtbuf.data(); + if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) + sample_format = FormatType::Int16; + else + { + /* Read the nBlockAlign field, and convert from bytes- to + * samples-per-block (verifying it's valid by converting back + * and comparing to the original value). + */ + byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8); + if(sample_format == FormatType::IMA4) + { + splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; + if(splblockalign < 1 + || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else if(sample_format == FormatType::MSADPCM) + { + splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; + if(splblockalign < 2 + || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else + sample_format = FormatType::Int16; + } + } + } + + if(sample_format == FormatType::Int16) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 2; + } + else if(sample_format == FormatType::Float) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 4; + } + + /* Figure out the OpenAL format from the file and desired sample type. */ + ALenum format{AL_NONE}; + if(sfinfo.channels == 1) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_MONO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_MONO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_MONO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_MONO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 2) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_STEREO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_STEREO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_STEREO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 3) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT2D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT2D_FLOAT32; + } + } + else if(sfinfo.channels == 4) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT3D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT3D_FLOAT32; + } + } + if(!format) + { + fmt::println(stderr, "Unsupported channel count: {}", sfinfo.channels); + return 0; + } + + if(sfinfo.frames/splblockalign > sf_count_t{std::numeric_limits::max()}/byteblockalign) + { + fmt::println(stderr, "Too many sample frames in {} ({})", filename, sfinfo.frames); + return 0; + } + + /* Decode the whole audio file to a buffer. */ + auto membuf = std::vector(static_cast(sfinfo.frames / splblockalign + * byteblockalign)); + + sf_count_t num_frames{}; + if(sample_format == FormatType::Int16) + num_frames = sf_readf_short(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else if(sample_format == FormatType::Float) + num_frames = sf_readf_float(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else + { + const sf_count_t count{sfinfo.frames / splblockalign * byteblockalign}; + num_frames = sf_read_raw(sndfile.get(), membuf.data(), count); + if(num_frames > 0) + num_frames = num_frames / byteblockalign * splblockalign; + } + if(num_frames < 1) + { + fmt::println(stderr, "Failed to read samples in {} ({})", filename, num_frames); + return 0; + } + + const auto num_bytes = static_cast(num_frames / splblockalign * byteblockalign); + + fmt::println("Loading: {} ({}, {}hz)", filename, FormatName(format), sfinfo.samplerate); + + ALuint buffer{}; + alGenBuffersDirect(context, 1, &buffer); + if(splblockalign > 1) + alBufferiDirect(context, buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); + alBufferDataDirect(context, buffer, format, membuf.data(), num_bytes, sfinfo.samplerate); + + /* Check if an error occurred, and clean up if so. */ + if(ALenum err{alGetErrorDirect(context)}; err != AL_NO_ERROR) + { + fmt::println(stderr, "OpenAL Error: {}", alGetStringDirect(context, err)); + if(buffer && alIsBufferDirect(context, buffer)) + alDeleteBuffersDirect(context, 1, &buffer); + return 0; + } + + return buffer; +} + + +int main(al::span args) +{ + /* Print out usage if no arguments were specified */ + if(args.size() < 2) + { + fmt::println(stderr, "Usage: {} [-device ] ", args[0]); + return 1; + } + + /* Initialize OpenAL. */ + args = args.subspan(1); + + ALCdevice *device{}; + if(args.size() > 1 && args[0] == "-device") + { + device = p_alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); + args = args.subspan(2); + } + if(!device) + device = p_alcOpenDevice(nullptr); + if(!device) + { + fmt::println(stderr, "Could not open a device!"); + return 1; + } + + if(!p_alcIsExtensionPresent(device, "ALC_EXT_direct_context")) + { + fmt::println(stderr, "ALC_EXT_direct_context not supported on device"); + p_alcCloseDevice(device); + return 1; + } + + /* On Windows with Creative's router, the device needs to be bootstrapped + * to use it through the driver directly. Otherwise the Direct functions + * aren't able to recognize the router's ALCcontexts. To handle this, we + * use the router's alcOpenDevice, alcGetProcAddress, and alcCloseDevice + * functions to open the device with the router, get the device driver's + * alcGetProcAddress2 function, and close the device with the router. Then + * call alcGetProcAddress2 with the null device handle to get the driver's + * functions. Afterward, we can open the device back up using the driver + * functions directly and continue on. + * + * Note that this will allow using other devices from the same driver just + * fine, but switching to a device on another driver will require using the + * original functions from the router (and require re-bootstrapping to use + * that driver's functions, if applicable). If controlling multiple devices + * with Direct functions from separate drivers simultaneously is desired, a + * good strategy may be to associate the driver's ALC and Direct functions + * with the ALCdevice and ALCcontext handles created from them. + * + * This is all unnecessary when not using Creative's router, including on + * non-Windows OSs or when using OpenAL Soft's router, where the original + * ALC functions can be used as normal. + */ + { + const std::string devname{alcGetString(device, ALC_ALL_DEVICES_SPECIFIER)}; + auto p_alcGetProcAddress2 = reinterpret_cast( + p_alcGetProcAddress(device, "alcGetProcAddress2")); + p_alcCloseDevice(device); + + /* Load the driver-specific ALC functions we'll be using. */ +#define LOAD_PROC(N) p_##N = reinterpret_cast(p_alcGetProcAddress2(nullptr, #N)) + LOAD_PROC(alcOpenDevice); + LOAD_PROC(alcCloseDevice); + LOAD_PROC(alcIsExtensionPresent); + LOAD_PROC(alcGetProcAddress); + LOAD_PROC(alcCreateContext); + LOAD_PROC(alcDestroyContext); + LOAD_PROC(alcGetProcAddress); +#undef LOAD_PROC + device = p_alcOpenDevice(devname.c_str()); + assert(device != nullptr); + } + + /* Load the Direct API functions we're using. */ +#define LOAD_PROC(N) N = reinterpret_cast(p_alcGetProcAddress(device, #N)) + LOAD_PROC(alGetStringDirect); + LOAD_PROC(alGetErrorDirect); + LOAD_PROC(alIsExtensionPresentDirect); + + LOAD_PROC(alGenBuffersDirect); + LOAD_PROC(alDeleteBuffersDirect); + LOAD_PROC(alIsBufferDirect); + LOAD_PROC(alBufferiDirect); + LOAD_PROC(alBufferDataDirect); + + LOAD_PROC(alGenSourcesDirect); + LOAD_PROC(alDeleteSourcesDirect); + LOAD_PROC(alSourceiDirect); + LOAD_PROC(alGetSourceiDirect); + LOAD_PROC(alGetSourcefDirect); + LOAD_PROC(alSourcePlayDirect); +#undef LOAD_PROC + + /* Create the context. It doesn't need to be set as current to use with the + * Direct API functions. + */ + ALCcontext *context{p_alcCreateContext(device, nullptr)}; + if(!context) + { + p_alcCloseDevice(device); + fmt::println(stderr, "Could not create a context!"); + return 1; + } + + /* Load the sound into a buffer. */ + const ALuint buffer{LoadSound(context, args[0])}; + if(!buffer) + { + p_alcDestroyContext(context); + p_alcCloseDevice(device); + return 1; + } + + /* Create the source to play the sound with. */ + ALuint source{0}; + alGenSourcesDirect(context, 1, &source); + alSourceiDirect(context, source, AL_BUFFER, static_cast(buffer)); + assert(alGetErrorDirect(context)==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play the sound until it finishes. */ + alSourcePlayDirect(context, source); + ALenum state{}; + do { + al_nssleep(10000000); + alGetSourceiDirect(context, source, AL_SOURCE_STATE, &state); + + /* Get the source offset. */ + ALfloat offset{}; + alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); + fmt::print(" \rOffset: {:.02f}", offset); + fflush(stdout); + } while(alGetErrorDirect(context) == AL_NO_ERROR && state == AL_PLAYING); + fmt::println(""); + + /* All done. Delete resources, and close down OpenAL. */ + alDeleteSourcesDirect(context, 1, &source); + alDeleteBuffersDirect(context, 1, &buffer); + + p_alcDestroyContext(context); + p_alcCloseDevice(device); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/libs/openal-soft/examples/alffplay.cpp b/libs/openal-soft/examples/alffplay.cpp index fb0a18da..28395999 100644 --- a/libs/openal-soft/examples/alffplay.cpp +++ b/libs/openal-soft/examples/alffplay.cpp @@ -4,29 +4,29 @@ * Requires C++14. */ -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include +#include #include #include +#include +#include +#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #ifdef __GNUC__ _Pragma("GCC diagnostic push") @@ -37,7 +37,6 @@ extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -#include "libavformat/version.h" #include "libavutil/avutil.h" #include "libavutil/error.h" #include "libavutil/frame.h" @@ -46,19 +45,27 @@ extern "C" { #include "libavutil/rational.h" #include "libavutil/samplefmt.h" #include "libavutil/time.h" -#include "libavutil/version.h" #include "libavutil/channel_layout.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" -constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; -constexpr auto AVErrorEOF = AVERROR_EOF; - struct SwsContext; } #define SDL_MAIN_HANDLED -#include "SDL.h" +#include "SDL3/SDL_events.h" +#include "SDL3/SDL_main.h" +#include "SDL3/SDL_render.h" +#include "SDL3/SDL_video.h" + +namespace { +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); +} +} // namespace #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif @@ -67,17 +74,18 @@ _Pragma("GCC diagnostic pop") #include "AL/al.h" #include "AL/alext.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" #include "common/alhelpers.h" +#include "fmt/core.h" +#include "fmt/format.h" namespace { -inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast(n); } - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - +using voidp = void*; using fixed32 = std::chrono::duration>; using nanoseconds = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; @@ -86,6 +94,16 @@ using seconds = std::chrono::seconds; using seconds_d64 = std::chrono::duration; using std::chrono::duration_cast; +#ifdef __GNUC__ +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#endif +constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; +constexpr auto AVErrorEOF = AVERROR_EOF; +#ifdef __GNUC__ +_Pragma("GCC diagnostic pop") +#endif + const std::string AppName{"alffplay"}; ALenum DirectOutMode{AL_FALSE}; @@ -116,7 +134,7 @@ constexpr milliseconds AudioBufferTotalTime{800}; constexpr auto AudioBufferCount = AudioBufferTotalTime / AudioBufferTime; enum { - FF_MOVIE_DONE_EVENT = SDL_USEREVENT + FF_MOVIE_DONE_EVENT = SDL_EVENT_USER }; enum class SyncMaster { @@ -168,14 +186,70 @@ struct SwsContextDeleter { using SwsContextPtr = std::unique_ptr; -struct ChannelLayout : public AVChannelLayout { - ChannelLayout() : AVChannelLayout{} { } - ~ChannelLayout() { av_channel_layout_uninit(this); } +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; } + + auto setPointer(const char *name, void *value) const + { return SDL_SetPointerProperty(mProperties, name, value); } + + auto setString(const char *name, const char *value) const + { return SDL_SetStringProperty(mProperties, name, value); } + + auto setInt(const char *name, Sint64 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}, +}; + + +struct ChannelLayout : public AVChannelLayout { + ChannelLayout() : AVChannelLayout{} { } + ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} + { av_channel_layout_copy(this, &rhs); } + ~ChannelLayout() { av_channel_layout_uninit(this); } + + auto operator=(const ChannelLayout &rhs) -> ChannelLayout& + { av_channel_layout_copy(this, &rhs); return *this; } }; -template class DataQueue { + const size_t mSizeLimit; std::mutex mPacketMutex, mFrameMutex; std::condition_variable mPacketCond; std::condition_variable mInFrameCond, mOutFrameCond; @@ -184,11 +258,10 @@ class DataQueue { size_t mTotalSize{0}; bool mFinished{false}; - AVPacketPtr getPacket() + auto getPacket() -> AVPacketPtr { - std::unique_lock plock{mPacketMutex}; - while(mPackets.empty() && !mFinished) - mPacketCond.wait(plock); + auto plock = std::unique_lock{mPacketMutex}; + mPacketCond.wait(plock, [this] { return !mPackets.empty() || mFinished; }); if(mPackets.empty()) return nullptr; @@ -199,36 +272,48 @@ class DataQueue { } public: + explicit DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } + int sendPacket(AVCodecContext *codecctx) { - AVPacketPtr packet{getPacket()}; + auto packet = getPacket(); - int ret{}; + auto ret = int{}; { - std::unique_lock flock{mFrameMutex}; - while((ret=avcodec_send_packet(codecctx, packet.get())) == AVERROR(EAGAIN)) - mInFrameCond.wait_for(flock, milliseconds{50}); + 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_one(); + return false; + }); } mOutFrameCond.notify_one(); if(!packet) { if(!ret) return AVErrorEOF; - std::cerr<< "Failed to send flush packet: "< flock{mFrameMutex}; - while((ret=avcodec_receive_frame(codecctx, frame)) == AVERROR(EAGAIN)) - mOutFrameCond.wait_for(flock, milliseconds{50}); + 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_one(); + return false; + }); } mInFrameCond.notify_one(); return ret; @@ -237,7 +322,7 @@ public: void setFinished() { { - std::lock_guard packetlock{mPacketMutex}; + auto plock = std::lock_guard{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); @@ -246,7 +331,7 @@ public: void flush() { { - std::lock_guard packetlock{mPacketMutex}; + auto plock = std::lock_guard{mPacketMutex}; mFinished = true; mPackets.clear(); @@ -255,21 +340,18 @@ public: mPacketCond.notify_one(); } - bool put(const AVPacket *pkt) + auto put(const AVPacket *pkt) -> bool { { - std::lock_guard packet_lock{mPacketMutex}; - if(mTotalSize >= SizeLimit || mFinished) + auto plock = std::lock_guard{mPacketMutex}; + if(mTotalSize >= mSizeLimit || mFinished) return false; - mPackets.push_back(AVPacketPtr{av_packet_alloc()}); - if(av_packet_ref(mPackets.back().get(), pkt) != 0) - { + auto *newpkt = mPackets.emplace_back(AVPacketPtr{av_packet_alloc()}).get(); + if(av_packet_ref(newpkt, pkt) == 0) + mTotalSize += static_cast(newpkt->size); + else mPackets.pop_back(); - return true; - } - - mTotalSize += static_cast(mPackets.back()->size); } mPacketCond.notify_one(); return true; @@ -285,7 +367,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue mQueue; + DataQueue mQueue{2_uz*1024_uz*1024_uz}; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -305,14 +387,13 @@ struct AudioState { AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE}; /* Storage of converted samples */ - uint8_t *mSamples{nullptr}; + std::array mSamples{}; + al::span mSamplesSpan; int mSamplesLen{0}; /* In samples */ int mSamplesPos{0}; int mSamplesMax{0}; - std::vector mBufferData_; - //std::unique_ptr mBufferData; - //size_t mBufferDataSize{0}; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; @@ -327,7 +408,7 @@ struct AudioState { std::array mBuffers{}; ALuint mBufferIdx{0}; - AudioState(MovieState &movie) : mMovie(movie) + explicit AudioState(MovieState &movie) : mMovie(movie) { mConnected.test_and_set(std::memory_order_relaxed); } ~AudioState() { @@ -336,7 +417,7 @@ struct AudioState { if(mBuffers[0]) alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); - av_freep(&mSamples); + av_freep(static_cast(mSamples.data())); } static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, @@ -360,7 +441,7 @@ struct AudioState { int getSync(); int decodeFrame(); - bool readAudio(uint8_t *samples, unsigned int length, int &sample_skip); + bool readAudio(al::span samples, unsigned int length, int &sample_skip); bool readAudio(int sample_skip); int handler(); @@ -372,7 +453,7 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue mQueue; + 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 @@ -385,7 +466,7 @@ struct VideoState { SwsContextPtr mSwscaleCtx; struct Picture { - AVFramePtr mFrame{}; + AVFramePtr mFrame; nanoseconds mPts{nanoseconds::min()}; }; std::array mPictQ; @@ -395,12 +476,14 @@ struct VideoState { 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}; - VideoState(MovieState &movie) : mMovie(movie) { } + explicit VideoState(MovieState &movie) : mMovie(movie) { } ~VideoState() { if(mImage) @@ -410,7 +493,7 @@ struct VideoState { nanoseconds getClock(); - void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const; + void display(SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -438,8 +521,7 @@ struct MovieState { std::string mFilename; - MovieState(std::string fname) - : mAudio(*this), mVideo(*this), mFilename(std::move(fname)) + explicit MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} { } ~MovieState() { @@ -457,7 +539,7 @@ struct MovieState { [[nodiscard]] nanoseconds getMasterClock(); [[nodiscard]] nanoseconds getDuration() const; - int streamComponentOpen(unsigned int stream_index); + bool streamComponentOpen(AVStream *stream); int parse_handler(); }; @@ -484,7 +566,7 @@ nanoseconds AudioState::getClockNoLock() return device_time - mDeviceStartTime - latency; } - if(!mBufferData_.empty()) + if(!mBufferData.empty()) { if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); @@ -521,7 +603,7 @@ nanoseconds AudioState::getClockNoLock() */ const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset>=roffset) ? woffset : (mBufferData_.size()+woffset)) - + const size_t readable{((woffset>=roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; pts = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; @@ -583,10 +665,10 @@ bool AudioState::startPlayback() { const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferData_.size()+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; - if(!mBufferData_.empty()) + if(!mBufferData.empty()) { if(readable == 0) return false; @@ -619,7 +701,7 @@ bool AudioState::startPlayback() * the device time the stream would have started at to reach where it * is now. */ - if(!mBufferData_.empty()) + if(!mBufferData.empty()) { nanoseconds startpts{mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate}; @@ -666,7 +748,7 @@ int AudioState::decodeFrame() while(int ret{mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())}) { if(ret == AVErrorEOF) return 0; - std::cerr<< "Failed to receive frame: "<nb_samples <= 0); @@ -677,18 +759,15 @@ int AudioState::decodeFrame() if(mDecodedFrame->nb_samples > mSamplesMax) { - av_freep(&mSamples); - av_samples_alloc(&mSamples, nullptr, mCodecCtx->ch_layout.nb_channels, + av_freep(static_cast(mSamples.data())); + av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; + mSamplesSpan = {mSamples[0], static_cast(mSamplesMax)*mFrameSize}; } - /* Copy to a local to mark const. Don't know why this can't be implicit. */ - using data_t = decltype(decltype(mDecodedFrame)::element_type::data); - std::array> cdata{}; - std::copy(std::begin(mDecodedFrame->data), std::end(mDecodedFrame->data), cdata.begin()); /* Return the amount of sample frames converted */ - const int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, - cdata.data(), mDecodedFrame->nb_samples)}; + const int data_size{swr_convert(mSwresCtx.get(), mSamples.data(), mDecodedFrame->nb_samples, + mDecodedFrame->extended_data, mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; @@ -698,15 +777,15 @@ int AudioState::decodeFrame() * multiple of the template type size. */ template -void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { - auto *sample = reinterpret_cast(in); - auto *dst = reinterpret_cast(out); + auto sample = al::span{reinterpret_cast(in.data()), in.size()/sizeof(T)}; + auto dst = al::span{reinterpret_cast(out.data()), out.size()/sizeof(T)}; /* NOTE: frame_size is a multiple of sizeof(T). */ - size_t type_mult{frame_size / sizeof(T)}; + const size_t type_mult{frame_size / sizeof(T)}; if(type_mult == 1) - std::fill_n(dst, count, *sample); + std::fill_n(dst.begin(), count, sample.front()); else for(size_t i{0};i < count;++i) { for(size_t j{0};j < type_mult;++j) @@ -714,7 +793,7 @@ void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size } } -void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { if((frame_size&7) == 0) sample_dup(out, in, count, frame_size); @@ -726,7 +805,7 @@ void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size sample_dup(out, in, count, frame_size); } -bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_skip) +bool AudioState::readAudio(al::span samples, unsigned int length, int &sample_skip) { unsigned int audio_size{0}; @@ -738,22 +817,22 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk unsigned int rem{length - audio_size}; if(mSamplesPos >= 0) { - const auto len = static_cast(mSamplesLen - mSamplesPos); - if(rem > len) rem = len; - std::copy_n(mSamples + static_cast(mSamplesPos)*size_t{mFrameSize}, - rem*size_t{mFrameSize}, samples); + rem = std::min(rem, static_cast(mSamplesLen - mSamplesPos)); + const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), rem*size_t{mFrameSize}, + samples.begin()); } else { rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ - sample_dup(samples, mSamples, rem, mFrameSize); + sample_dup(samples, mSamplesSpan, rem, mFrameSize); } mSamplesPos += static_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; - samples += rem*size_t{mFrameSize}; + samples = samples.subspan(rem*size_t{mFrameSize}); audio_size += rem; while(mSamplesPos >= mSamplesLen) @@ -778,7 +857,7 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk if(audio_size < length) { const unsigned int rem{length - audio_size}; - std::fill_n(samples, rem*mFrameSize, + std::fill_n(samples.begin(), rem*mFrameSize, (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } @@ -792,17 +871,17 @@ bool AudioState::readAudio(int sample_skip) while(mSamplesLen > 0) { const size_t nsamples{((roffset > woffset) ? roffset-woffset-1 - : (roffset == 0) ? (mBufferData_.size()-woffset-1) - : (mBufferData_.size()-woffset)) / mFrameSize}; + : (roffset == 0) ? (mBufferData.size()-woffset-1) + : (mBufferData.size()-woffset)) / mFrameSize}; if(!nsamples) break; if(mSamplesPos < 0) { const size_t rem{std::min(nsamples, static_cast(-mSamplesPos))}; - sample_dup(&mBufferData_[woffset], mSamples, rem, mFrameSize); + sample_dup(al::span{mBufferData}.subspan(woffset), mSamplesSpan, rem, mFrameSize); woffset += rem * mFrameSize; - if(woffset == mBufferData_.size()) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -814,9 +893,10 @@ bool AudioState::readAudio(int sample_skip) const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; const size_t nbytes{rem * mFrameSize}; - memcpy(&mBufferData_[woffset], mSamples + boffset, nbytes); + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), nbytes, + mBufferData.begin()+ptrdiff_t(woffset)); woffset += nbytes; - if(woffset == mBufferData_.size()) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -853,21 +933,19 @@ void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALui return; } - std::cout<< "\n---- AL Event on AudioState "<(length)}<<"\n----"<< - std::endl; + fmt::println("\n" + "Object ID: {}\n" + "Parameter: {}\n" + "Message: {}\n----", + object, param, std::string_view{message, static_cast(length)}); if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { @@ -881,23 +959,24 @@ void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALui ALsizei AudioState::bufferCallback(void *data, ALsizei size) noexcept { + auto dst = al::span{static_cast(data), static_cast(size)}; ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(!dst.empty()) { const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; if(woffset == roffset) break; - size_t todo{((woffset < roffset) ? mBufferData_.size() : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; + todo = std::min(todo, dst.size()); - memcpy(data, &mBufferData_[roffset], todo); - data = static_cast(data) + todo; + std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); + dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; - if(roffset == mBufferData_.size()) + if(roffset == mBufferData.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); @@ -915,7 +994,7 @@ int AudioState::handler() AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, AL_EVENT_TYPE_DISCONNECTED_SOFT}}; - EventControlManager(milliseconds &sleep_time) + explicit EventControlManager(milliseconds &sleep_time) { if(alEventControlSOFT) { @@ -940,6 +1019,18 @@ int AudioState::handler() std::vector samples; ALsizei buffer_len{0}; + /* Note that ffmpeg assumes AmbiX (ACN layout, SN3D normalization). Only + * support HOA when OpenAL can take AmbiX natively (if AmbiX -> FuMa + * conversion is needed, we don't bother with higher order channels). + */ + const auto has_bfmt = bool{alIsExtensionPresent("AL_EXT_BFORMAT") != AL_FALSE}; + const auto has_bfmt_ex = bool{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; + const auto has_bfmt_hoa = bool{has_bfmt_ex + && alIsExtensionPresent("AL_SOFT_bformat_hoa") != AL_FALSE}; + /* AL_SOFT_bformat_hoa supports up to 14th order (225 channels). */ + const auto max_ambi_order = has_bfmt_hoa ? 14 : 1; + auto ambi_order = 0; + /* Find a suitable format for OpenAL. */ const auto layoutmask = mCodecCtx->ch_layout.u.mask; /* NOLINT(*-union-access) */ mDstChanLayout = 0; @@ -975,7 +1066,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN_FLOAT32_SOFT + : alGetEnumValue("AL_FORMAT_QUAD32"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -985,8 +1077,7 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO_FLOAT32; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + 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 @@ -994,14 +1085,16 @@ int AudioState::handler() * which we can ignore, so check for that too. */ auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; - if(channels == mCodecCtx->ch_layout.nb_channels - || channels+2 == mCodecCtx->ch_layout.nb_channels) + auto channels = ALuint(order+1) * ALuint(order+1); + if(channels == ALuint(mCodecCtx->ch_layout.nb_channels) + || channels+2 == ALuint(mCodecCtx->ch_layout.nb_channels)) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which - * is 4 channels for 3D buffers. + * is 4 channels for 3D buffers, unless AL_SOFT_bformat_hoa is + * also supported. */ - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32"); } } @@ -1036,7 +1129,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN8_SOFT + : alGetEnumValue("AL_FORMAT_QUAD8"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -1046,15 +1140,15 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO8; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; + auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); } } @@ -1089,7 +1183,8 @@ int AudioState::handler() { mDstChanLayout = layoutmask; mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN16_SOFT + : alGetEnumValue("AL_FORMAT_QUAD16"); } } if(layoutmask == AV_CH_LAYOUT_MONO) @@ -1099,15 +1194,15 @@ int AudioState::handler() mFormat = AL_FORMAT_MONO16; } } - else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC - && alIsExtensionPresent("AL_EXT_BFORMAT")) + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; - int channels{(order+1) * (order+1)}; + auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { - mFrameSize *= 4; + ambi_order = std::min(order, max_ambi_order); + mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); } } @@ -1119,7 +1214,8 @@ int AudioState::handler() } } - mSamples = nullptr; + mSamples.fill(nullptr); + mSamplesSpan = {}; mSamplesMax = 0; mSamplesPos = 0; mSamplesLen = 0; @@ -1127,45 +1223,38 @@ int AudioState::handler() mDecodedFrame.reset(av_frame_alloc()); if(!mDecodedFrame) { - std::cerr<< "Failed to allocate audio frame" <sample_rate, - &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; - mSwresCtx.reset(ps); + const auto err = swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, + mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, + mCodecCtx->sample_rate, 0, nullptr); if(err != 0) { std::array errstr{}; - std::cerr<< "Failed to allocate SwrContext: " - < 1) + fmt::println("Found AL_SOFT_bformat_hoa (order {})", ambi_order); + else if(has_bfmt_ex) + fmt::println("Found AL_SOFT_bformat_ex"); else { - std::cout<< "Found AL_EXT_BFORMAT" < mtx(size_t{64}*64, 0.0); + std::vector mtx(64_uz*64_uz, 0.0); mtx[0 + 0*64] = std::sqrt(0.5); mtx[3 + 1*64] = 1.0; mtx[1 + 2*64] = 1.0; @@ -1178,23 +1267,22 @@ int AudioState::handler() ChannelLayout layout{}; av_channel_layout_from_mask(&layout, mDstChanLayout); - SwrContext *ps{}; - int err{swr_alloc_set_opts2(&ps, &layout, mDstSampleFmt, mCodecCtx->sample_rate, - &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; - mSwresCtx.reset(ps); + int err{swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, + mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, + mCodecCtx->sample_rate, 0, nullptr)}; if(err != 0) { std::array errstr{}; - std::cerr<< "Failed to allocate SwrContext: " - < errstr{}; - std::cerr<< "Failed to initialize audio converter: " - <(M_PI / 3.0), static_cast(-M_PI / 3.0)}; + static constexpr std::array angles{static_cast(al::numbers::pi / 3.0), + static_cast(-al::numbers::pi / 3.0)}; alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { for(ALuint bufid : mBuffers) { - alBufferi(bufid, AL_AMBISONIC_LAYOUT_SOFT, ambi_layout); - alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, ambi_scale); + alBufferi(bufid, AL_AMBISONIC_LAYOUT_SOFT, AL_ACN_SOFT); + alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, AL_SN3D_SOFT); } } + if(ambi_order > 1) + { + for(ALuint bufid : mBuffers) + alBufferi(bufid, AL_UNPACK_AMBISONIC_ORDER_SOFT, ambi_order); + } #ifdef AL_SOFT_UHJ if(EnableSuperStereo) alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); @@ -1231,17 +1325,17 @@ int AudioState::handler() alSourcei(mSource, AL_BUFFER, static_cast(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { - fprintf(stderr, "Failed to set buffer callback\n"); + fmt::println(stderr, "Failed to set buffer callback"); alSourcei(mSource, AL_BUFFER, 0); } else { - mBufferData_.resize(static_cast(duration_cast(mCodecCtx->sample_rate * + mBufferData.resize(static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTotalTime).count()) * mFrameSize); - std::fill(mBufferData_.begin(), mBufferData_.end(), uint8_t{}); + std::fill(mBufferData.begin(), mBufferData.end(), uint8_t{}); mReadPos.store(0, std::memory_order_relaxed); - mWritePos.store(mBufferData_.size()/mFrameSize/2*mFrameSize, std::memory_order_relaxed); + mWritePos.store(mBufferData.size()/mFrameSize/2*mFrameSize, std::memory_order_relaxed); ALCint refresh{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); @@ -1264,7 +1358,7 @@ int AudioState::handler() if(ret == AVErrorEOF) break; } }; - auto sender = std::async(std::launch::async, packet_sender); + auto sender [[maybe_unused]] = std::async(std::launch::async, packet_sender); srclock.lock(); if(alcGetInteger64vSOFT) @@ -1301,7 +1395,7 @@ int AudioState::handler() } ALenum state; - if(!mBufferData_.empty()) + if(!mBufferData.empty()) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); @@ -1331,7 +1425,7 @@ int AudioState::handler() /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ - if(!readAudio(samples.data(), static_cast(buffer_len), sync_skip)) + if(!readAudio(samples, static_cast(buffer_len), sync_skip)) break; const ALuint bufid{mBuffers[mBufferIdx]}; @@ -1373,8 +1467,7 @@ int AudioState::handler() break; } if(ALenum err{alGetError()}) - std::cerr<< "Got AL error: 0x"<width - static_cast(frame->crop_left + frame->crop_right); + auto frame_height = frame->height - static_cast(frame->crop_top + frame->crop_bottom); - int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; - int frame_height{frame->height - static_cast(frame->crop_top + frame->crop_bottom)}; - if(frame->sample_aspect_ratio.num == 0) - aspect_ratio = 0.0; - else - { - aspect_ratio = av_q2d(frame->sample_aspect_ratio) * frame_width / - frame_height; - } - if(aspect_ratio <= 0.0) - aspect_ratio = static_cast(frame_width) / frame_height; + const auto src_rect = SDL_FRect{ static_cast(frame->crop_left), + static_cast(frame->crop_top), static_cast(frame_width), + static_cast(frame_height) }; - SDL_GetWindowSize(screen, &win_w, &win_h); - h = win_h; - w = (static_cast(std::rint(h * aspect_ratio)) + 3) & ~3; - if(w > win_w) - { - w = win_w; - h = (static_cast(std::rint(w / aspect_ratio)) + 3) & ~3; - } - x = (win_w - w) / 2; - y = (win_h - h) / 2; - - SDL_Rect src_rect{ static_cast(frame->crop_left), static_cast(frame->crop_top), - frame_width, frame_height }; - SDL_Rect dst_rect{ x, y, w, h }; - SDL_RenderCopy(renderer, mImage, &src_rect, &dst_rect); + SDL_RenderTexture(renderer, mImage, &src_rect, nullptr); SDL_RenderPresent(renderer); } @@ -1483,18 +1553,174 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re mPictQCond.notify_one(); /* allocate or resize the buffer! */ - bool fmt_updated{false}; - if(!mImage || mWidth != frame->width || mHeight != frame->height) + if(!mImage || mWidth != frame->width || mHeight != frame->height + || frame->format != mAVFormat) { - fmt_updated = true; if(mImage) SDL_DestroyTexture(mImage); - mImage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, - frame->width, frame->height); - if(!mImage) - std::cerr<< "Failed to create YV12 texture!" <width; - mHeight = frame->height; + mImage = nullptr; + mSwscaleCtx = nullptr; + + auto fmtiter = std::find_if(TextureFormatMap.begin(), TextureFormatMap.end(), + [frame](const TextureFormatEntry &entry) noexcept + { return frame->format == entry.avformat; }); + if(fmtiter != TextureFormatMap.end()) + { + auto props = SDLProps{}; + props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, fmtiter->sdlformat); + props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); + 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); + props.setInt(SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace); + + mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); + if(!mImage) + fmt::println(stderr, "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(stderr, "Could not find SDL texture format for pix_fmt {0:#x} ({0})", + as_unsigned(frame->format)); + + auto props = SDLProps{}; + props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_RGB24); + props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); + props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); + props.setInt(SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, frame->height); + + mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); + if(!mImage) + fmt::println(stderr, "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, static_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); + } } int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; @@ -1512,48 +1738,45 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re else if(aspect_ratio > 0.0) frame_height = static_cast(std::lround(frame_height / aspect_ratio)); } - SDL_SetWindowSize(screen, frame_width, frame_height); + if(SDL_SetWindowSize(screen, frame_width, frame_height)) + SDL_SyncWindow(screen); + SDL_SetRenderLogicalPresentation(renderer, frame_width, frame_height, + SDL_LOGICAL_PRESENTATION_LETTERBOX); } if(mImage) { - void *pixels{nullptr}; - int pitch{0}; - - if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) + 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(SDL_LockTexture(mImage, nullptr, &pixels, &pitch) != 0) - std::cerr<< "Failed to lock texture" <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) { - // Convert the image into YUV format that SDL uses - int w{frame->width}; - int h{frame->height}; - if(!mSwscaleCtx || fmt_updated) + auto pixels = voidp{}; + auto pitch = int{}; + if(!SDL_LockTexture(mImage, nullptr, &pixels, &pitch)) + fmt::println(stderr, "Failed to lock texture: {}", SDL_GetError()); + else { - mSwscaleCtx.reset(sws_getContext( - w, h, mCodecCtx->pix_fmt, - w, h, AV_PIX_FMT_YUV420P, 0, - nullptr, nullptr, nullptr - )); + /* 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); } - - /* point pict at the queue */ - const std::array pict_data{ - static_cast(pixels), - static_cast(pixels) + ptrdiff_t{w}*h, - static_cast(pixels) + ptrdiff_t{w}*h + ptrdiff_t{w}*h/4 - }; - const std::array pict_linesize{pitch, pitch/2, pitch/2}; - - sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), - 0, h, pict_data.data(), pict_linesize.data()); - SDL_UnlockTexture(mImage); } + else + SDL_UpdateTexture(mImage, nullptr, frame->data[0], frame->linesize[0]); redraw = true; } @@ -1562,7 +1785,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re if(redraw) { /* Show the picture! */ - display(screen, renderer, frame); + display(renderer, frame); } if(updated) @@ -1591,15 +1814,14 @@ int VideoState::handler() { pict.mFrame = AVFramePtr{av_frame_alloc()}; }); /* Prefill the codec buffer. */ - auto packet_sender = [this]() + auto sender [[maybe_unused]] = std::async(std::launch::async, [this]() { while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; } - }; - auto sender = std::async(std::launch::async, packet_sender); + }); { std::lock_guard displock{mDispPtsMutex}; @@ -1613,12 +1835,17 @@ int VideoState::handler() Picture *vp{&mPictQ[write_idx]}; /* Retrieve video frame. */ - AVFrame *decoded_frame{vp->mFrame.get()}; - while(int ret{mQueue.receiveFrame(mCodecCtx.get(), decoded_frame)}) + auto get_frame = [this](AVFrame *frame) -> AVFrame* { - if(ret == AVErrorEOF) goto finish; - std::cerr<< "Failed to receive frame: "<mFrame.get()); + if(!decoded_frame) break; /* Get the PTS for this frame. */ if(decoded_frame->best_effort_timestamp != AVNoPtsValue) @@ -1640,16 +1867,16 @@ int VideoState::handler() if(write_idx == mPictQRead.load(std::memory_order_acquire)) { /* Wait until we have space for a new pic */ - std::unique_lock lock{mPictQMutex}; - while(write_idx == mPictQRead.load(std::memory_order_acquire)) - mPictQCond.wait(lock); + auto lock = std::unique_lock{mPictQMutex}; + mPictQCond.wait(lock, [write_idx,this]() noexcept + { return write_idx != mPictQRead.load(std::memory_order_acquire); }); } } -finish: + mEOS = true; - std::unique_lock lock{mPictQMutex}; - while(!mFinalUpdate) mPictQCond.wait(lock); + auto lock = std::unique_lock{mPictQMutex}; + mPictQCond.wait(lock, [this]() noexcept { return mFinalUpdate.load(); }); return 0; } @@ -1662,39 +1889,36 @@ int MovieState::decode_interrupt_cb(void *ctx) bool MovieState::prepare() { - AVIOContext *avioctx{nullptr}; - AVIOInterruptCB intcb{decode_interrupt_cb, this}; - if(avio_open2(&avioctx, mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr)) + auto intcb = AVIOInterruptCB{decode_interrupt_cb, this}; + if(avio_open2(al::out_ptr(mIOContext), mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr) < 0) { - std::cerr<< "Failed to open "<pb = mIOContext.get(); - fmtctx->interrupt_callback = intcb; - if(avformat_open_input(&fmtctx, mFilename.c_str(), nullptr, nullptr) != 0) + 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) { - std::cerr<< "Failed to open "< slock{mStartupMutex}; while(!mStartupDone) mStartupCond.wait(slock); @@ -1703,12 +1927,12 @@ bool MovieState::prepare() void MovieState::setTitle(SDL_Window *window) const { - auto pos1 = mFilename.rfind('/'); - auto pos2 = mFilename.rfind('\\'); - auto fpos = ((pos1 == std::string::npos) ? pos2 : - (pos2 == std::string::npos) ? pos1 : - std::max(pos1, pos2)) + 1; - SDL_SetWindowTitle(window, (mFilename.substr(fpos)+" - "+AppName).c_str()); + /* rfind returns npos if the char isn't found, and npos+1==0, which will + * give the desired result for finding the filename portion. + */ + const auto fpos = std::max(mFilename.rfind('/')+1, mFilename.rfind('\\')+1); + const auto title = fmt::format("{} - {}", std::string_view{mFilename}.substr(fpos), AppName); + SDL_SetWindowTitle(window, title.c_str()); } nanoseconds MovieState::getClock() const @@ -1730,46 +1954,43 @@ nanoseconds MovieState::getMasterClock() nanoseconds MovieState::getDuration() const { return std::chrono::duration>(mFormatCtx->duration); } -int MovieState::streamComponentOpen(unsigned int stream_index) +bool MovieState::streamComponentOpen(AVStream *stream) { - if(stream_index >= mFormatCtx->nb_streams) - return -1; - /* Get a pointer to the codec context for the stream, and open the * associated codec. */ AVCodecCtxPtr avctx{avcodec_alloc_context3(nullptr)}; - if(!avctx) return -1; + if(!avctx) return false; - if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar)) - return -1; + if(avcodec_parameters_to_context(avctx.get(), stream->codecpar)) + return false; const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { - std::cerr<< "Unsupported codec: "<codec_id) - << " (0x"<codec_id<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 = mFormatCtx->streams[stream_index]; + mAudio.mStream = stream; mAudio.mCodecCtx = std::move(avctx); - break; + return true; case AVMEDIA_TYPE_VIDEO: - mVideo.mStream = mFormatCtx->streams[stream_index]; + mVideo.mStream = stream; mVideo.mCodecCtx = std::move(avctx); - break; + return true; default: - return -1; + break; } - return static_cast(stream_index); + return false; } int MovieState::parse_handler() @@ -1781,13 +2002,16 @@ int MovieState::parse_handler() int audio_index{-1}; /* Find the first video and audio streams */ - for(unsigned int i{0u};i < mFormatCtx->nb_streams;i++) + const auto ctxstreams = al::span{mFormatCtx->streams, mFormatCtx->nb_streams}; + for(size_t i{0};i < ctxstreams.size();++i) { - auto codecpar = mFormatCtx->streams[i]->codecpar; - if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0) - video_index = streamComponentOpen(i); - else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) - audio_index = streamComponentOpen(i); + auto codecpar = ctxstreams[i]->codecpar; + if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0 + && streamComponentOpen(ctxstreams[i])) + video_index = static_cast(i); + else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0 + && streamComponentOpen(ctxstreams[i])) + audio_index = static_cast(i); } { @@ -1798,7 +2022,7 @@ int MovieState::parse_handler() if(video_index < 0 && audio_index < 0) { - std::cerr<< mFilename<<": could not open codecs" <= 0) - mAudioThread = std::thread{std::mem_fn(&AudioState::handler), &mAudio}; + mAudioThread = std::thread{&AudioState::handler, &mAudio}; if(video_index >= 0) - mVideoThread = std::thread{std::mem_fn(&VideoState::handler), &mVideo}; + mVideoThread = std::thread{&VideoState::handler, &mVideo}; /* Main packet reading/dispatching loop */ AVPacketPtr packet{av_packet_alloc()}; @@ -1862,132 +2086,86 @@ void MovieState::stop() } -// Helper class+method to print the time with human-readable formatting. -struct PrettyTime { - seconds mTime; -}; -std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) +// Helper method to print the time with human-readable formatting. +auto PrettyTime(seconds t) -> std::string { - using hours = std::chrono::hours; using minutes = std::chrono::minutes; + using hours = std::chrono::hours; - seconds t{rhs.mTime}; if(t.count() < 0) - { - os << '-'; - t *= -1; - } + return "0s"; // Only handle up to hour formatting if(t >= hours{1}) - os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) - << (duration_cast(t).count() % 60) << 'm'; - else - os << duration_cast(t).count() << 'm' << std::setfill('0'); - os << std::setw(2) << (duration_cast(t).count() % 60) << 's' << std::setw(0) - << std::setfill(' '); - return os; + 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); } -} // namespace - - -int main(int argc, char *argv[]) +int main(al::span args) { SDL_SetMainReady(); - std::unique_ptr movState; - - if(argc < 2) + if(args.size() < 2) { - std::cerr<< "Usage: "<] [-direct] " <] [-direct] ", args[0]); return 1; } - /* Register all formats and codecs */ -#if !(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)) - av_register_all(); -#endif + /* Initialize networking protocols */ avformat_network_init(); - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) + if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { - std::cerr<< "Could not initialize SDL - <<"<( - alcGetProcAddress(device, "alcGetInteger64vSOFT") - ); + alcGetProcAddress(device, "alcGetInteger64vSOFT")); } } if(alIsExtensionPresent("AL_SOFT_source_latency")) { - std::cout<< "Found AL_SOFT_source_latency" <( - alGetProcAddress("alGetSourcei64vSOFT") - ); + alGetProcAddress("alGetSourcei64vSOFT")); } if(alIsExtensionPresent("AL_SOFT_events")) { - std::cout<< "Found AL_SOFT_events" <( alGetProcAddress("alEventControlSOFT")); alEventCallbackSOFT = reinterpret_cast( @@ -1995,73 +2173,74 @@ int main(int argc, char *argv[]) } if(alIsExtensionPresent("AL_SOFT_callback_buffer")) { - std::cout<< "Found AL_SOFT_callback_buffer" <( alGetProcAddress("alBufferCallbackSOFT")); } - int fileidx{0}; - for(;fileidx < argc;++fileidx) + size_t fileidx{0}; + for(;fileidx < args.size();++fileidx) { - if(strcmp(argv[fileidx], "-direct") == 0) + if(args[fileidx] == "-direct") { if(alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { - std::cout<< "Found AL_SOFT_direct_channels_remix" <{}; + while(fileidx < args.size() && !movState) { - movState = std::make_unique(argv[fileidx++]); + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(!movState) { - std::cerr<< "Could not start a video" <setTitle(screen); @@ -2073,32 +2252,32 @@ int main(int argc, char *argv[]) seconds last_time{seconds::min()}; while(true) { - /* SDL_WaitEventTimeout is broken, just force a 10ms sleep. */ - std::this_thread::sleep_for(milliseconds{10}); + auto event = SDL_Event{}; + auto have_event = SDL_WaitEventTimeout(&event, 10); - auto cur_time = std::chrono::duration_cast(movState->getMasterClock()); + const auto cur_time = duration_cast(movState->getMasterClock()); if(cur_time != last_time) { - auto end_time = std::chrono::duration_cast(movState->getDuration()); - std::cout<< " \r "<(movState->getDuration()); + fmt::print(" \r {} / {}", PrettyTime(cur_time), PrettyTime(end_time)); + fflush(stdout); last_time = cur_time; } - bool force_redraw{false}; - SDL_Event event{}; - while(SDL_PollEvent(&event) != 0) + auto force_redraw = false; + while(have_event) { switch(event.type) { - case SDL_KEYDOWN: - switch(event.key.keysym.sym) + case SDL_EVENT_KEY_DOWN: + switch(event.key.key) { case SDLK_ESCAPE: movState->stop(); eom_action = EomAction::Quit; break; - case SDLK_n: + case SDLK_N: movState->stop(); eom_action = EomAction::Next; break; @@ -2108,38 +2287,31 @@ int main(int argc, char *argv[]) } break; - case SDL_WINDOWEVENT: - switch(event.window.event) - { - case SDL_WINDOWEVENT_RESIZED: - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderFillRect(renderer, nullptr); - force_redraw = true; - break; - - case SDL_WINDOWEVENT_EXPOSED: - force_redraw = true; - break; - - default: - 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_QUIT: + case SDL_EVENT_QUIT: movState->stop(); eom_action = EomAction::Quit; break; case FF_MOVIE_DONE_EVENT: - std::cout<<'\n'; + fmt::println(""); last_time = seconds::min(); if(eom_action != EomAction::Quit) { movState = nullptr; - while(fileidx < argc && !movState) + while(fileidx < args.size() && !movState) { - movState = std::make_unique(argv[fileidx++]); + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(movState) @@ -2159,17 +2331,28 @@ int main(int argc, char *argv[]) SDL_DestroyWindow(screen); screen = nullptr; - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS); exit(0); default: break; } + have_event = SDL_PollEvent(&event); } movState->mVideo.updateVideo(screen, renderer, force_redraw); } - std::cerr<< "SDL_WaitEvent error - "<= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/libs/openal-soft/examples/allafplay.cpp b/libs/openal-soft/examples/allafplay.cpp new file mode 100644 index 00000000..e99ed544 --- /dev/null +++ b/libs/openal-soft/examples/allafplay.cpp @@ -0,0 +1,1012 @@ +/* + * OpenAL LAF Playback Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for playback of Limitless Audio Format files. + * + * Some current shortcomings: + * + * - 256 track limit. Could be made higher, but making it too flexible would + * necessitate more micro-allocations. + * + * - "Objects" mode only supports sample rates that are a multiple of 48. Since + * positions are specified as samples in extra channels/tracks, and 3*16 + * samples are needed per track to specify the full set of positions, and + * each chunk is exactly one second long, other sample rates would result in + * the positions being split across chunks, causing the source playback + * offset to go out of sync with the offset used to look up the current + * spatial positions. Fixing this will require slightly more work to update + * and synchronize the spatial position arrays against the playback offset. + * + * - Updates are specified as fast as the app can detect and react to the + * reported source offset (that in turn depends on how often OpenAL renders). + * This can cause some positions to be a touch late and lose some granular + * temporal movement. In practice, this should probably be good enough for + * most use-cases. Fixing this would need either a new extension to queue + * position changes to apply when needed, or use a separate loopback device + * to render with and control the number of samples rendered between updates + * (with a second device to do the actual playback). + * + * - The LAF documentation doesn't prohibit object position tracks from being + * separated with audio tracks in between, or from being the first tracks + * followed by the audio tracks. It's not known if this is intended to be + * allowed, but it's not supported. Object position tracks must be last. + * + * Some remaining issues: + * + * - There are bursts of static on some channels. This doesn't appear to be a + * parsing error since the bursts last less than the chunk size, and it never + * loses sync with the remaining chunks. Might be an encoding error with the + * files tested. + * + * - Positions are specified in left-handed coordinates, despite the LAF + * documentation saying it's right-handed. Might be an encoding error with + * the files tested, or might be a misunderstanding about which is which. How + * to proceed may depend on how wide-spread this issue ends up being, but for + * now, they're treated as left-handed here. + * + * - The LAF documentation doesn't specify the range or direction for the + * channels' X and Y axis rotation in Channels mode. Presumably X rotation + * (elevation) goes from -pi/2...+pi/2 and Y rotation (azimuth) goes from + * either -pi...+pi or 0...pi*2, but the direction of movement isn't + * specified. Currently positive azimuth moves from center rightward and + * positive elevation moves from head-level upward. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" + +#include "albit.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "common/alhelpers.h" +#include "filesystem.h" +#include "fmt/core.h" +#include "fmt/std.h" + +#include "win_main_utf8.h" + +namespace { + +/* Filter object functions */ +auto alGenFilters = LPALGENFILTERS{}; +auto alDeleteFilters = LPALDELETEFILTERS{}; +auto alIsFilter = LPALISFILTER{}; +auto alFilteri = LPALFILTERI{}; +auto alFilteriv = LPALFILTERIV{}; +auto alFilterf = LPALFILTERF{}; +auto alFilterfv = LPALFILTERFV{}; +auto alGetFilteri = LPALGETFILTERI{}; +auto alGetFilteriv = LPALGETFILTERIV{}; +auto alGetFilterf = LPALGETFILTERF{}; +auto alGetFilterfv = LPALGETFILTERFV{}; + +/* Effect object functions */ +auto alGenEffects = LPALGENEFFECTS{}; +auto alDeleteEffects = LPALDELETEEFFECTS{}; +auto alIsEffect = LPALISEFFECT{}; +auto alEffecti = LPALEFFECTI{}; +auto alEffectiv = LPALEFFECTIV{}; +auto alEffectf = LPALEFFECTF{}; +auto alEffectfv = LPALEFFECTFV{}; +auto alGetEffecti = LPALGETEFFECTI{}; +auto alGetEffectiv = LPALGETEFFECTIV{}; +auto alGetEffectf = LPALGETEFFECTF{}; +auto alGetEffectfv = LPALGETEFFECTFV{}; + +/* Auxiliary Effect Slot object functions */ +auto alGenAuxiliaryEffectSlots = LPALGENAUXILIARYEFFECTSLOTS{}; +auto alDeleteAuxiliaryEffectSlots = LPALDELETEAUXILIARYEFFECTSLOTS{}; +auto alIsAuxiliaryEffectSlot = LPALISAUXILIARYEFFECTSLOT{}; +auto alAuxiliaryEffectSloti = LPALAUXILIARYEFFECTSLOTI{}; +auto alAuxiliaryEffectSlotiv = LPALAUXILIARYEFFECTSLOTIV{}; +auto alAuxiliaryEffectSlotf = LPALAUXILIARYEFFECTSLOTF{}; +auto alAuxiliaryEffectSlotfv = LPALAUXILIARYEFFECTSLOTFV{}; +auto alGetAuxiliaryEffectSloti = LPALGETAUXILIARYEFFECTSLOTI{}; +auto alGetAuxiliaryEffectSlotiv = LPALGETAUXILIARYEFFECTSLOTIV{}; +auto alGetAuxiliaryEffectSlotf = LPALGETAUXILIARYEFFECTSLOTF{}; +auto alGetAuxiliaryEffectSlotfv = LPALGETAUXILIARYEFFECTSLOTFV{}; + + +auto MuteFilterID = ALuint{}; +auto LowFrequencyEffectID = ALuint{}; +auto LfeSlotID = ALuint{}; + + +using namespace std::string_view_literals; + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) +{ + auto errstr = fmt::format("{}:{}: {}: {}", filename, linenum, funcname, message); + throw std::runtime_error{errstr}; +} + +#define MyAssert(cond) do { \ + if(!(cond)) UNLIKELY \ + do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, \ + std::data(__func__)); \ +} while(0) + + +enum class Quality : std::uint8_t { + s8, s16, f32, s24 +}; +enum class Mode : bool { + Channels, Objects +}; + +auto GetQualityName(Quality quality) noexcept -> std::string_view +{ + switch(quality) + { + case Quality::s8: return "8-bit int"sv; + case Quality::s16: return "16-bit int"sv; + case Quality::f32: return "32-bit float"sv; + case Quality::s24: return "24-bit int"sv; + } + return ""sv; +} + +auto GetModeName(Mode mode) noexcept -> std::string_view +{ + switch(mode) + { + case Mode::Channels: return "channels"sv; + case Mode::Objects: return "objects"sv; + } + return ""sv; +} + +auto BytesFromQuality(Quality quality) noexcept -> size_t +{ + switch(quality) + { + case Quality::s8: return 1; + case Quality::s16: return 2; + case Quality::f32: return 4; + case Quality::s24: return 3; + } + return 4; +} + +auto BufferBytesFromQuality(Quality quality) noexcept -> size_t +{ + switch(quality) + { + case Quality::s8: return 1; + case Quality::s16: return 2; + case Quality::f32: return 4; + /* 24-bit samples are converted to 32-bit for OpenAL. */ + case Quality::s24: return 4; + } + return 4; +} + + +/* Helper class for reading little-endian samples on big-endian targets, or + * convert 24-bit samples. + */ +template +struct SampleReader; + +template<> +struct SampleReader { + using src_t = int8_t; + using dst_t = int8_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t { return in; } +}; + +template<> +struct SampleReader { + using src_t = int16_t; + using dst_t = int16_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + if constexpr(al::endian::native == al::endian::little) + return in; + else + return al::byteswap(in); + } +}; + +template<> +struct SampleReader { + /* 32-bit float samples are read as 32-bit integer on big-endian systems, + * so that they can be byteswapped before being reinterpreted as float. + */ + using src_t = std::conditional_t; + using dst_t = float; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + if constexpr(al::endian::native == al::endian::little) + return in; + else + return al::bit_cast(al::byteswap(static_cast(in))); + } +}; + +template<> +struct SampleReader { + /* 24-bit samples are converted to 32-bit integer. */ + using src_t = std::array; + using dst_t = int32_t; + + [[nodiscard]] static + auto read(const src_t &in) noexcept -> dst_t + { + return static_cast((uint32_t{in[0]}<<8) | (uint32_t{in[1]}<<16) + | (uint32_t{in[2]}<<24)); + } +}; + + +/* Each track with position data consists of a set of 3 samples per 16 audio + * channels, resulting in a full set of positions being specified over 48 + * sample frames. + */ +constexpr auto FramesPerPos = 48_uz; + +struct Channel { + ALuint mSource{}; + std::array mBuffers{}; + float mAzimuth{}; + float mElevation{}; + bool mIsLfe{}; + + Channel() = default; + Channel(const Channel&) = delete; + Channel(Channel&& rhs) + : mSource{rhs.mSource}, mBuffers{rhs.mBuffers}, mAzimuth{rhs.mAzimuth} + , mElevation{rhs.mElevation}, mIsLfe{rhs.mIsLfe} + { + rhs.mSource = 0; + rhs.mBuffers.fill(0); + } + ~Channel() + { + if(mSource) alDeleteSources(1, &mSource); + if(mBuffers[0]) alDeleteBuffers(ALsizei(mBuffers.size()), mBuffers.data()); + } + + auto operator=(const Channel&) -> Channel& = delete; + auto operator=(Channel&& rhs) -> Channel& + { + std::swap(mSource, rhs.mSource); + std::swap(mBuffers, rhs.mBuffers); + std::swap(mAzimuth, rhs.mAzimuth); + std::swap(mElevation, rhs.mElevation); + std::swap(mIsLfe, rhs.mIsLfe); + return *this; + } +}; + +struct LafStream { + std::filebuf mInFile; + + Quality mQuality{}; + Mode mMode{}; + uint32_t mNumTracks{}; + uint32_t mSampleRate{}; + ALenum mALFormat{}; + uint64_t mSampleCount{}; + + uint64_t mCurrentSample{}; + + std::array mEnabledTracks{}; + uint32_t mNumEnabled{}; + std::vector mSampleChunk; + al::span mSampleLine; + + std::vector mChannels; + std::vector> mPosTracks; + + LafStream() = default; + LafStream(const LafStream&) = delete; + ~LafStream() = default; + auto operator=(const LafStream&) -> LafStream& = delete; + + [[nodiscard]] + auto readChunk() -> uint32_t; + + void convertSamples(const al::span samples) const; + + void convertPositions(const al::span dst, const al::span src) const; + + template + void copySamples(char *dst, const char *src, size_t idx, size_t count) const; + + [[nodiscard]] + auto prepareTrack(size_t trackidx, size_t count) -> al::span; + + [[nodiscard]] + auto isAtEnd() const noexcept -> bool { return mCurrentSample >= mSampleCount; } +}; + +auto LafStream::readChunk() -> uint32_t +{ + mEnabledTracks.fill(0); + + mInFile.sgetn(reinterpret_cast(mEnabledTracks.data()), (mNumTracks+7_z)>>3); + mNumEnabled = std::accumulate(mEnabledTracks.cbegin(), mEnabledTracks.cend(), 0u, + [](const unsigned int val, const uint8_t in) + { return val + unsigned(al::popcount(unsigned(in))); }); + + /* Make sure enable bits aren't set for non-existent tracks. */ + if(mEnabledTracks[((mNumTracks+7_uz)>>3) - 1] >= (1u<<(mNumTracks&7))) + throw std::runtime_error{"Invalid channel enable bits"}; + + /* Each chunk is exactly one second long, with samples interleaved for each + * enabled track. The last chunk may be shorter if there isn't enough time + * remaining for a full second. + */ + const auto numsamples = std::min(uint64_t{mSampleRate}, mSampleCount-mCurrentSample); + + const auto toread = std::streamsize(numsamples * BytesFromQuality(mQuality) * mNumEnabled); + if(mInFile.sgetn(mSampleChunk.data(), toread) != toread) + throw std::runtime_error{"Failed to read sample chunk"}; + + std::fill(mSampleChunk.begin()+toread, mSampleChunk.end(), char{}); + + mCurrentSample += numsamples; + return static_cast(numsamples); +} + +void LafStream::convertSamples(const al::span samples) const +{ + /* OpenAL uses unsigned 8-bit samples (0...255), so signed 8-bit samples + * (-128...+127) need conversion. The other formats are fine. + */ + if(mQuality == Quality::s8) + std::transform(samples.begin(), samples.end(), samples.begin(), + [](const char sample) noexcept { return char(sample^0x80); }); +} + +void LafStream::convertPositions(const al::span dst, const al::span src) const +{ + switch(mQuality) + { + case Quality::s8: + std::transform(src.begin(), src.end(), dst.begin(), + [](const int8_t in) { return float(in) / 127.0f; }); + break; + case Quality::s16: + { + auto i16src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(int16_t)}; + std::transform(i16src.begin(), i16src.end(), dst.begin(), + [](const int16_t in) { return float(in) / 32767.0f; }); + } + break; + case Quality::f32: + { + auto f32src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(float)}; + std::copy(f32src.begin(), f32src.end(), dst.begin()); + } + break; + case Quality::s24: + { + /* 24-bit samples are converted to 32-bit in copySamples. */ + auto i32src = al::span{reinterpret_cast(src.data()), + src.size()/sizeof(int32_t)}; + std::transform(i32src.begin(), i32src.end(), dst.begin(), + [](const int32_t in) { return float(in>>8) / 8388607.0f; }); + } + break; + } +} + +template +void LafStream::copySamples(char *dst, const char *src, const size_t idx, const size_t count) const +{ + using reader_t = SampleReader; + using src_t = typename reader_t::src_t; + using dst_t = typename reader_t::dst_t; + + const auto step = size_t{mNumEnabled}; + assert(idx < step); + + auto input = al::span{reinterpret_cast(src), count*step}; + auto output = al::span{reinterpret_cast(dst), count}; + + auto inptr = input.begin(); + std::generate_n(output.begin(), output.size(), [&inptr,idx,step] + { + auto ret = reader_t::read(inptr[idx]); + inptr += ptrdiff_t(step); + return ret; + }); +} + +auto LafStream::prepareTrack(const size_t trackidx, const size_t count) -> al::span +{ + const auto todo = std::min(size_t{mSampleRate}, count); + if((mEnabledTracks[trackidx>>3] & (1_uz<<(trackidx&7)))) + { + /* If the track is enabled, get the real index (skipping disabled + * tracks), and deinterlace it into the mono line. + */ + const auto idx = [this,trackidx]() -> unsigned int + { + const auto bits = al::span{mEnabledTracks}.first(trackidx>>3); + const auto res = std::accumulate(bits.begin(), bits.end(), 0u, + [](const unsigned int val, const uint8_t in) + { return val + unsigned(al::popcount(unsigned(in))); }); + return unsigned(al::popcount(mEnabledTracks[trackidx>>3] & ((1u<<(trackidx&7))-1))) + + res; + }(); + + switch(mQuality) + { + case Quality::s8: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::s16: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::f32: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + case Quality::s24: + copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); + break; + } + } + else + { + /* If the track is disabled, provide silence. */ + std::fill_n(mSampleLine.begin(), mSampleLine.size(), char{}); + } + + return mSampleLine.first(todo * BufferBytesFromQuality(mQuality)); +} + + +auto LoadLAF(const fs::path &fname) -> std::unique_ptr +{ + auto laf = std::make_unique(); + if(!laf->mInFile.open(fname, std::ios_base::binary | std::ios_base::in)) + throw std::runtime_error{"Could not open file"}; + + auto marker = std::array{}; + if(laf->mInFile.sgetn(marker.data(), marker.size()) != marker.size()) + throw std::runtime_error{"Failed to read file marker"}; + if(std::string_view{marker.data(), marker.size()} != "LIMITLESS"sv) + throw std::runtime_error{"Not an LAF file"}; + + auto header = std::array{}; + if(laf->mInFile.sgetn(header.data(), header.size()) != header.size()) + throw std::runtime_error{"Failed to read header"}; + while(std::string_view{header.data(), 4} != "HEAD"sv) + { + auto headview = std::string_view{header.data(), header.size()}; + auto hiter = header.begin(); + if(const auto hpos = std::min(headview.find("HEAD"sv), headview.size()); + hpos < headview.size()) + { + /* Found the HEAD marker. Copy what was read of the header to the + * front, fill in the rest of the header, and continue loading. + */ + hiter = std::copy(header.begin()+hpos, header.end(), hiter); + } + else if(al::ends_with(headview, "HEA"sv)) + { + /* Found what might be the HEAD marker at the end. Copy it to the + * front, refill the header, and check again. + */ + hiter = std::copy_n(header.end()-3, 3, hiter); + } + else if(al::ends_with(headview, "HE"sv)) + hiter = std::copy_n(header.end()-2, 2, hiter); + else if(headview.back() == 'H') + hiter = std::copy_n(header.end()-1, 1, hiter); + + const auto toread = std::distance(hiter, header.end()); + if(laf->mInFile.sgetn(al::to_address(hiter), toread) != toread) + throw std::runtime_error{"Failed to read header"}; + } + + laf->mQuality = [stype=int{header[4]}] { + if(stype == 0) return Quality::s8; + if(stype == 1) return Quality::s16; + if(stype == 2) return Quality::f32; + if(stype == 3) return Quality::s24; + throw std::runtime_error{fmt::format("Invalid quality type: {}", stype)}; + }(); + + laf->mMode = [mode=int{header[5]}] { + if(mode == 0) return Mode::Channels; + if(mode == 1) return Mode::Objects; + throw std::runtime_error{fmt::format("Invalid mode: {}", mode)}; + }(); + + laf->mNumTracks = [input=al::span{header}.subspan<6,4>()] { + return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + }(); + + fmt::println("Filename: {}", fname.string()); + fmt::println(" quality: {}", GetQualityName(laf->mQuality)); + fmt::println(" mode: {}", GetModeName(laf->mMode)); + fmt::println(" track count: {}", laf->mNumTracks); + + if(laf->mNumTracks == 0) + throw std::runtime_error{"No tracks"}; + if(laf->mNumTracks > 256) + throw std::runtime_error{fmt::format("Too many tracks: {}", laf->mNumTracks)}; + + auto chandata = std::vector(laf->mNumTracks*9_uz); + auto headersize = std::streamsize(chandata.size()); + if(laf->mInFile.sgetn(chandata.data(), headersize) != headersize) + throw std::runtime_error{"Failed to read channel header data"}; + + if(laf->mMode == Mode::Channels) + laf->mChannels.reserve(laf->mNumTracks); + else + { + if(laf->mNumTracks < 2) + throw std::runtime_error{"Not enough tracks"}; + + auto numchans = uint32_t{laf->mNumTracks - 1}; + auto numpostracks = uint32_t{1}; + while(numpostracks*16 < numchans) + { + --numchans; + ++numpostracks; + } + laf->mChannels.reserve(numchans); + laf->mPosTracks.reserve(numpostracks); + } + + for(uint32_t i{0};i < laf->mNumTracks;++i) + { + static constexpr auto read_float = [](al::span input) + { + const auto value = uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + return al::bit_cast(value); + }; + + auto chan = al::span{chandata}.subspan(i*9_uz, 9); + auto x_axis = read_float(chan.first<4>()); + auto y_axis = read_float(chan.subspan<4,4>()); + auto lfe_flag = int{chan[8]}; + + fmt::println("Track {}: E={:f}, A={:f} (LFE: {})", i, x_axis, y_axis, lfe_flag); + + if(x_axis != x_axis && y_axis == 0.0) + { + MyAssert(laf->mMode == Mode::Objects); + MyAssert(i != 0); + laf->mPosTracks.emplace_back(); + } + else + { + MyAssert(laf->mPosTracks.empty()); + MyAssert(std::isfinite(x_axis) && std::isfinite(y_axis)); + auto &channel = laf->mChannels.emplace_back(); + channel.mAzimuth = y_axis; + channel.mElevation = x_axis; + channel.mIsLfe = lfe_flag != 0; + } + } + fmt::println("Channels: {}", laf->mChannels.size()); + + /* For "objects" mode, ensure there's enough tracks with position data to + * handle the audio channels. + */ + if(laf->mMode == Mode::Objects) + MyAssert(((laf->mChannels.size()-1)>>4) == laf->mPosTracks.size()-1); + + auto footer = std::array{}; + if(laf->mInFile.sgetn(footer.data(), footer.size()) != footer.size()) + throw std::runtime_error{"Failed to read sample header data"}; + + laf->mSampleRate = [input=al::span{footer}.first<4>()] { + return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) + | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); + }(); + laf->mSampleCount = [input=al::span{footer}.last<8>()] { + return uint64_t{uint8_t(input[0])} | (uint64_t{uint8_t(input[1])}<<8) + | (uint64_t{uint8_t(input[2])}<<16u) | (uint64_t{uint8_t(input[3])}<<24u) + | (uint64_t{uint8_t(input[4])}<<32u) | (uint64_t{uint8_t(input[5])}<<40u) + | (uint64_t{uint8_t(input[6])}<<48u) | (uint64_t{uint8_t(input[7])}<<56u); + }(); + fmt::println("Sample rate: {}", laf->mSampleRate); + fmt::println("Length: {} samples ({:.2f} sec)", laf->mSampleCount, + static_cast(laf->mSampleCount)/static_cast(laf->mSampleRate)); + + /* Position vectors get split across the PCM chunks if the sample rate + * isn't a multiple of 48. Each PCM chunk is exactly one second (the sample + * rate in sample frames). Each track with position data consists of a set + * of 3 samples for 16 audio channels, resuling in 48 sample frames for a + * full set of positions. Extra logic will be needed to manage the position + * frame offset separate from each chunk. + */ + MyAssert(laf->mMode == Mode::Channels || (laf->mSampleRate%FramesPerPos) == 0); + + for(size_t i{0};i < laf->mPosTracks.size();++i) + laf->mPosTracks[i].resize(laf->mSampleRate*2_uz, 0.0f); + + laf->mSampleChunk.resize(laf->mSampleRate*BytesFromQuality(laf->mQuality)*laf->mNumTracks + + laf->mSampleRate*BufferBytesFromQuality(laf->mQuality)); + laf->mSampleLine = al::span{laf->mSampleChunk}.last(laf->mSampleRate + * BufferBytesFromQuality(laf->mQuality)); + + return laf; +} + +void PlayLAF(std::string_view fname) +try { + auto laf = LoadLAF(fs::u8path(fname)); + + switch(laf->mQuality) + { + case Quality::s8: + laf->mALFormat = AL_FORMAT_MONO8; + break; + case Quality::s16: + laf->mALFormat = AL_FORMAT_MONO16; + break; + case Quality::f32: + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + laf->mALFormat = AL_FORMAT_MONO_FLOAT32; + break; + case Quality::s24: + laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO32"); + if(!laf->mALFormat || laf->mALFormat == -1) + laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO_I32"); + break; + } + if(!laf->mALFormat || laf->mALFormat == -1) + throw std::runtime_error{fmt::format("No supported format for {} samples", + GetQualityName(laf->mQuality))}; + + static constexpr auto alloc_channel = [](Channel &channel) + { + alGenSources(1, &channel.mSource); + alGenBuffers(ALsizei(channel.mBuffers.size()), channel.mBuffers.data()); + + /* Disable distance attenuation, and make sure the source stays locked + * relative to the listener. + */ + alSourcef(channel.mSource, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(channel.mSource, AL_SOURCE_RELATIVE, AL_TRUE); + + /* FIXME: Is the Y rotation/azimuth clockwise or counter-clockwise? + * Does +azimuth move a front sound right or left? + */ + const auto x = std::sin(channel.mAzimuth) * std::cos(channel.mElevation); + const auto y = std::sin(channel.mElevation); + const auto z = -std::cos(channel.mAzimuth) * std::cos(channel.mElevation); + alSource3f(channel.mSource, AL_POSITION, x, y, z); + + if(channel.mIsLfe) + { + if(LfeSlotID) + { + /* For LFE, silence the direct/dry path and connect the LFE aux + * slot on send 0. + */ + alSourcei(channel.mSource, AL_DIRECT_FILTER, ALint(MuteFilterID)); + alSource3i(channel.mSource, AL_AUXILIARY_SEND_FILTER, ALint(LfeSlotID), 0, + AL_FILTER_NULL); + } + else + { + /* If AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT isn't available, + * silence LFE channels since they may not be appropriate to + * play normally. + */ + alSourcef(channel.mSource, AL_GAIN, 0.0f); + } + } + + if(auto err=alGetError()) + throw std::runtime_error{fmt::format("OpenAL error: {}", alGetString(err))}; + }; + std::for_each(laf->mChannels.begin(), laf->mChannels.end(), alloc_channel); + + while(!laf->isAtEnd()) + { + auto state = ALenum{}; + auto offset = ALint{}; + auto processed = ALint{}; + /* All sources are played in sync, so they'll all be at the same offset + * with the same state and number of processed buffers. Query the back + * source just in case the previous update ran really late and missed + * updating only some sources on time (in which case, the latter ones + * will underrun, which this will detect and restart them all as + * needed). + */ + alGetSourcei(laf->mChannels.back().mSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + + if(state == AL_PLAYING || state == AL_PAUSED) + { + if(!laf->mPosTracks.empty()) + { + alcSuspendContext(alcGetCurrentContext()); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); + const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; + const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; + const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; + + /* Contrary to the docs, the position is left-handed and + * needs to be converted to right-handed. + */ + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + alcProcessContext(alcGetCurrentContext()); + } + + if(processed > 0) + { + const auto numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + + auto bufid = ALuint{}; + alSourceUnqueueBuffers(laf->mChannels[i].mSource, 1, &bufid); + alBufferData(bufid, laf->mALFormat, samples.data(), ALsizei(samples.size()), + ALsizei(laf->mSampleRate)); + alSourceQueueBuffers(laf->mChannels[i].mSource, 1, &bufid); + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + std::copy(laf->mPosTracks[i].begin() + ptrdiff_t(laf->mSampleRate), + laf->mPosTracks[i].end(), laf->mPosTracks[i].begin()); + + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), + positions); + } + } + else + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + } + else if(state == AL_STOPPED) + { + auto sources = std::array{}; + for(size_t i{0};i < laf->mChannels.size();++i) + sources[i] = laf->mChannels[i].mSource; + alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); + } + else if(state == AL_INITIAL) + { + auto sources = std::array{}; + auto numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + alBufferData(laf->mChannels[i].mBuffers[0], laf->mALFormat, samples.data(), + ALsizei(samples.size()), ALsizei(laf->mSampleRate)); + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.first(laf->mSampleRate), + positions); + } + + numsamples = laf->readChunk(); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto samples = laf->prepareTrack(i, numsamples); + laf->convertSamples(samples); + alBufferData(laf->mChannels[i].mBuffers[1], laf->mALFormat, samples.data(), + ALsizei(samples.size()), ALsizei(laf->mSampleRate)); + alSourceQueueBuffers(laf->mChannels[i].mSource, + ALsizei(laf->mChannels[i].mBuffers.size()), laf->mChannels[i].mBuffers.data()); + sources[i] = laf->mChannels[i].mSource; + } + for(size_t i{0};i < laf->mPosTracks.size();++i) + { + const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); + laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), + positions); + } + + if(!laf->mPosTracks.empty()) + { + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto x = laf->mPosTracks[trackidx][(i&15)*3 + 0]; + const auto y = laf->mPosTracks[trackidx][(i&15)*3 + 1]; + const auto z = laf->mPosTracks[trackidx][(i&15)*3 + 2]; + + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + } + + alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); + } + else + break; + } + + auto state = ALenum{}; + auto offset = ALint{}; + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + while(alGetError() == AL_NO_ERROR && state == AL_PLAYING) + { + if(!laf->mPosTracks.empty()) + { + alcSuspendContext(alcGetCurrentContext()); + for(size_t i{0};i < laf->mChannels.size();++i) + { + const auto trackidx = i>>4; + + const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); + const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; + const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; + const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; + + alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); + } + alcProcessContext(alcGetCurrentContext()); + } + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); + } +} +catch(std::exception& e) { + fmt::println(stderr, "Error playing {}:\n {}", fname, e.what()); +} + +auto main(al::span args) -> int +{ + /* Print out usage if no arguments were specified */ + if(args.size() < 2) + { + fmt::println(stderr, "Usage: {} [-device ] \n", args[0]); + return 1; + } + args = args.subspan(1); + + if(InitAL(args) != 0) + throw std::runtime_error{"Failed to initialize OpenAL"}; + /* A simple RAII container for automating OpenAL shutdown. */ + struct AudioManager { + AudioManager() = default; + AudioManager(const AudioManager&) = delete; + auto operator=(const AudioManager&) -> AudioManager& = delete; + ~AudioManager() + { + if(LfeSlotID) + { + alDeleteAuxiliaryEffectSlots(1, &LfeSlotID); + alDeleteEffects(1, &LowFrequencyEffectID); + alDeleteFilters(1, &MuteFilterID); + } + CloseAL(); + } + }; + AudioManager almgr; + + if(auto *device = alcGetContextsDevice(alcGetCurrentContext()); + alcIsExtensionPresent(device, "ALC_EXT_EFX") + && alcIsExtensionPresent(device, "ALC_EXT_DEDICATED")) + { +#define LOAD_PROC(x) do { \ + x = reinterpret_cast(alGetProcAddress(#x)); \ + if(!x) fmt::println(stderr, "Failed to find function '{}'\n", #x##sv);\ + } while(0) + LOAD_PROC(alGenFilters); + LOAD_PROC(alDeleteFilters); + LOAD_PROC(alIsFilter); + LOAD_PROC(alFilterf); + LOAD_PROC(alFilterfv); + LOAD_PROC(alFilteri); + LOAD_PROC(alFilteriv); + LOAD_PROC(alGetFilterf); + LOAD_PROC(alGetFilterfv); + LOAD_PROC(alGetFilteri); + LOAD_PROC(alGetFilteriv); + LOAD_PROC(alGenEffects); + LOAD_PROC(alDeleteEffects); + LOAD_PROC(alIsEffect); + LOAD_PROC(alEffectf); + LOAD_PROC(alEffectfv); + LOAD_PROC(alEffecti); + LOAD_PROC(alEffectiv); + LOAD_PROC(alGetEffectf); + LOAD_PROC(alGetEffectfv); + LOAD_PROC(alGetEffecti); + LOAD_PROC(alGetEffectiv); + LOAD_PROC(alGenAuxiliaryEffectSlots); + LOAD_PROC(alDeleteAuxiliaryEffectSlots); + LOAD_PROC(alIsAuxiliaryEffectSlot); + LOAD_PROC(alAuxiliaryEffectSlotf); + LOAD_PROC(alAuxiliaryEffectSlotfv); + LOAD_PROC(alAuxiliaryEffectSloti); + LOAD_PROC(alAuxiliaryEffectSlotiv); + LOAD_PROC(alGetAuxiliaryEffectSlotf); + LOAD_PROC(alGetAuxiliaryEffectSlotfv); + LOAD_PROC(alGetAuxiliaryEffectSloti); + LOAD_PROC(alGetAuxiliaryEffectSlotiv); +#undef LOAD_PROC + + alGenFilters(1, &MuteFilterID); + alFilteri(MuteFilterID, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(MuteFilterID, AL_LOWPASS_GAIN, 0.0f); + MyAssert(alGetError() == AL_NO_ERROR); + + alGenEffects(1, &LowFrequencyEffectID); + alEffecti(LowFrequencyEffectID, AL_EFFECT_TYPE, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT); + MyAssert(alGetError() == AL_NO_ERROR); + + alGenAuxiliaryEffectSlots(1, &LfeSlotID); + alAuxiliaryEffectSloti(LfeSlotID, AL_EFFECTSLOT_EFFECT, ALint(LowFrequencyEffectID)); + MyAssert(alGetError() == AL_NO_ERROR); + } + + std::for_each(args.begin(), args.end(), PlayLAF); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + MyAssert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/libs/openal-soft/examples/alloopback.c b/libs/openal-soft/examples/alloopback.c index 964a0cdb..62f21512 100644 --- a/libs/openal-soft/examples/alloopback.c +++ b/libs/openal-soft/examples/alloopback.c @@ -29,12 +29,14 @@ #include #include #include +#include +#include #define SDL_MAIN_HANDLED -#include "SDL.h" -#include "SDL_audio.h" -#include "SDL_error.h" -#include "SDL_stdinc.h" +#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" @@ -42,13 +44,6 @@ #include "common/alhelpers.h" -#ifndef SDL_AUDIO_MASK_BITSIZE -#define SDL_AUDIO_MASK_BITSIZE (0xFF) -#endif -#ifndef SDL_AUDIO_BITSIZE -#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE) -#endif - #ifndef M_PI #define M_PI (3.14159265358979323846) #endif @@ -58,6 +53,8 @@ typedef struct { ALCcontext *Context; ALCsizei FrameSize; + void *Buffer; + int BufferSize; } PlaybackInfo; static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT; @@ -65,10 +62,26 @@ static LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT; static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT; -void SDLCALL RenderSDLSamples(void *userdata, Uint8 *stream, int len) +void SDLCALL RenderSDLSamples(void *userdata, SDL_AudioStream *stream, int additional_amount, + int total_amount) { PlaybackInfo *playback = (PlaybackInfo*)userdata; - alcRenderSamplesSOFT(playback->Device, stream, len/playback->FrameSize); + + 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); } @@ -134,8 +147,9 @@ static ALuint CreateSineWave(void) int main(int argc, char *argv[]) { - PlaybackInfo playback = { NULL, NULL, 0 }; - SDL_AudioSpec desired, obtained; + PlaybackInfo playback = { NULL, NULL, 0, NULL, 0 }; + SDL_AudioStream *stream = NULL; + SDL_AudioSpec obtained; ALuint source, buffer; ALCint attrs[16]; ALenum state; @@ -158,25 +172,25 @@ int main(int argc, char *argv[]) LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT); #undef LOAD_PROC - if(SDL_Init(SDL_INIT_AUDIO) == -1) + 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 requested format and callback. */ - desired.channels = 2; - desired.format = AUDIO_S16SYS; - desired.freq = 44100; - desired.padding = 0; - desired.samples = 4096; - desired.callback = RenderSDLSamples; - desired.userdata = &playback; - if(SDL_OpenAudio(&desired, &obtained) != 0) + /* 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) { - SDL_Quit(); fprintf(stderr, "Failed to open SDL audio: %s\n", SDL_GetError()); - return 1; + 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. */ @@ -185,6 +199,14 @@ int main(int argc, char *argv[]) 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); @@ -192,17 +214,15 @@ int main(int argc, char *argv[]) } attrs[2] = ALC_FORMAT_TYPE_SOFT; - if(obtained.format == AUDIO_U8) + if(obtained.format == SDL_AUDIO_U8) attrs[3] = ALC_UNSIGNED_BYTE_SOFT; - else if(obtained.format == AUDIO_S8) + else if(obtained.format == SDL_AUDIO_S8) attrs[3] = ALC_BYTE_SOFT; - else if(obtained.format == AUDIO_U16SYS) - attrs[3] = ALC_UNSIGNED_SHORT_SOFT; - else if(obtained.format == AUDIO_S16SYS) + else if(obtained.format == SDL_AUDIO_S16) attrs[3] = ALC_SHORT_SOFT; - else if(obtained.format == AUDIO_S32SYS) + else if(obtained.format == SDL_AUDIO_S32) attrs[3] = ALC_INT_SOFT; - else if(obtained.format == AUDIO_F32SYS) + else if(obtained.format == SDL_AUDIO_F32) attrs[3] = ALC_FLOAT_SOFT; else { @@ -215,7 +235,7 @@ int main(int argc, char *argv[]) attrs[6] = 0; /* end of list */ - playback.FrameSize = obtained.channels * SDL_AUDIO_BITSIZE(obtained.format) / 8; + playback.FrameSize = obtained.channels * (int)SDL_AUDIO_BITSIZE(obtained.format) / 8; /* Initialize OpenAL loopback device, using our format attributes. */ playback.Device = alcLoopbackOpenDeviceSOFT(NULL); @@ -228,7 +248,7 @@ int main(int argc, char *argv[]) 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]); + ChannelsName(attrs[1]), TypeName(attrs[3]), attrs[5]); goto error; } playback.Context = alcCreateContext(playback.Device, attrs); @@ -238,20 +258,18 @@ int main(int argc, char *argv[]) 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_PauseAudio(0); + * start being called regularly to update the AL playback state. + */ + SDL_ResumeAudioStreamDevice(stream); /* Load the sound into a buffer. */ buffer = CreateSineWave(); if(!buffer) - { - SDL_CloseAudio(); - alcDestroyContext(playback.Context); - alcCloseDevice(playback.Device); - SDL_Quit(); - return 1; - } + goto error; /* Create the source to play the sound with. */ source = 0; @@ -271,23 +289,25 @@ int main(int argc, char *argv[]) alDeleteBuffers(1, &buffer); /* Stop SDL playing. */ - SDL_PauseAudio(1); + SDL_PauseAudioStreamDevice(stream); /* Close up OpenAL and SDL. */ - SDL_CloseAudio(); + SDL_DestroyAudioStream(stream); alcDestroyContext(playback.Context); alcCloseDevice(playback.Device); - SDL_Quit(); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); return 0; error: - SDL_CloseAudio(); + if(stream) + SDL_DestroyAudioStream(stream); if(playback.Context) alcDestroyContext(playback.Context); if(playback.Device) alcCloseDevice(playback.Device); - SDL_Quit(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); return 1; } diff --git a/libs/openal-soft/examples/alreverb.c b/libs/openal-soft/examples/alreverb.c index 15b92235..5f68fe3a 100644 --- a/libs/openal-soft/examples/alreverb.c +++ b/libs/openal-soft/examples/alreverb.c @@ -77,16 +77,17 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) ALuint effect = 0; ALenum err; + /* Clear error state. */ + alGetError(); + /* Create the effect object and check if we can do EAX reverb. */ alGenEffects(1, &effect); - if(alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + err = alGetError(); + if(err == AL_NO_ERROR) { printf("Using EAX Reverb\n"); - /* EAX Reverb is available. Set the EAX effect type then load the - * reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); @@ -116,7 +117,8 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) printf("Using Standard Reverb\n"); /* No EAX Reverb. Set the standard reverb effect type then load the - * available reverb properties. */ + * available reverb properties. + */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); diff --git a/libs/openal-soft/examples/alstreamcb.cpp b/libs/openal-soft/examples/alstreamcb.cpp index ebf3d3b0..e799c064 100644 --- a/libs/openal-soft/examples/alstreamcb.cpp +++ b/libs/openal-soft/examples/alstreamcb.cpp @@ -25,14 +25,18 @@ /* This file contains a streaming audio player using a callback buffer. */ +#include #include +#include #include -#include +#include #include #include +#include #include #include #include +#include #include #include @@ -42,7 +46,10 @@ #include "AL/alc.h" #include "AL/alext.h" +#include "alnumeric.h" +#include "alspan.h" #include "common/alhelpers.h" +#include "fmt/core.h" #include "win_main_utf8.h" @@ -58,7 +65,7 @@ struct StreamPlayer { /* A lockless ring-buffer (supports single-provider, single-consumer * operation). */ - std::vector mBufferData; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; size_t mSamplesPerBlock{1}; @@ -115,15 +122,16 @@ struct StreamPlayer { } } - bool open(const char *filename) + bool open(const std::string &filename) { close(); /* Open the file and figure out the OpenAL format. */ - mSndfile = sf_open(filename, SFM_READ, &mSfInfo); + mSndfile = sf_open(filename.c_str(), SFM_READ, &mSfInfo); if(!mSndfile) { - fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile)); + fmt::println(stderr, "Could not open audio in {}: {}", filename, + sf_strerror(mSndfile)); return false; } @@ -253,7 +261,7 @@ struct StreamPlayer { } if(!mFormat) { - fprintf(stderr, "Unsupported channel count: %d\n", mSfInfo.channels); + fmt::println(stderr, "Unsupported channel count: {}", mSfInfo.channels); sf_close(mSndfile); mSndfile = nullptr; @@ -280,6 +288,9 @@ struct StreamPlayer { { return static_cast(userptr)->bufferCallback(data, size); } ALsizei bufferCallback(void *data, ALsizei size) noexcept { + const auto output = al::span{static_cast(data), static_cast(size)}; + auto dst = output.begin(); + /* NOTE: The callback *MUST* be real-time safe! That means no blocking, * no allocations or deallocations, no I/O, no page faults, or calls to * functions that could do these things (this includes calling to @@ -287,10 +298,9 @@ struct StreamPlayer { * unexpectedly stall this call since the audio has to get to the * device on time. */ - ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(const auto remaining = static_cast(std::distance(dst, output.end()))) { /* If the write offset == read offset, there's nothing left in the * ring-buffer. Break from the loop and give what has been written. @@ -304,15 +314,14 @@ struct StreamPlayer { * amount to copy given how much is remaining to write. */ size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + todo = std::min(todo, remaining); /* Copy from the ring buffer to the provided output buffer. Wrap * the resulting read offset if it reached the end of the ring- * buffer. */ - memcpy(data, &mBufferData[roffset], todo); - data = static_cast(data) + todo; - got += static_cast(todo); + const auto input = al::span{mBufferData}.subspan(roffset, todo); + dst = std::copy_n(input.begin(), input.size(), dst); roffset += todo; if(roffset == mBufferData.size()) @@ -323,7 +332,7 @@ struct StreamPlayer { */ mReadPos.store(roffset, std::memory_order_release); - return got; + return static_cast(std::distance(output.begin(), dst)); } bool prepare() @@ -334,7 +343,8 @@ struct StreamPlayer { alSourcei(mSource, AL_BUFFER, static_cast(mBuffer)); if(ALenum err{alGetError()}) { - fprintf(stderr, "Failed to set callback: %s (0x%04x)\n", alGetString(err), err); + fmt::println(stderr, "Failed to set callback: {} ({:#x})", alGetString(err), + as_unsigned(err)); return false; } return true; @@ -358,95 +368,72 @@ struct StreamPlayer { * For a playing/paused source, it's the source's offset including * the playback offset the source was started with. */ - const size_t curtime{((state == AL_STOPPED) + const auto curtime = ((state == AL_STOPPED) ? (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock - : (static_cast(pos) + mStartOffset/mBytesPerBlock*mSamplesPerBlock)) - / static_cast(mSfInfo.samplerate)}; - printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferData.size()); + : (size_t{static_cast(pos)} + mStartOffset)) + / static_cast(mSfInfo.samplerate); + fmt::print("\r {}m{:02}s ({:3}% full)", curtime/60, curtime%60, + readable * 100 / mBufferData.size()); } else - fputs("Starting...", stdout); + fmt::println("Starting..."); fflush(stdout); while(!sf_error(mSndfile)) { - size_t read_bytes; - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - if(roffset > woffset) + const auto roffset = mReadPos.load(std::memory_order_relaxed); + auto get_writable = [this,roffset,woffset]() noexcept -> size_t { - /* Note that the ring buffer's writable space is one byte less - * than the available area because the write offset ending up - * at the read offset would be interpreted as being empty - * instead of full. - */ - const size_t writable{(roffset-woffset-1) / mBytesPerBlock}; - if(!writable) break; - - if(mSampleFormat == SampleType::Int16) + if(roffset > woffset) { - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else if(mSampleFormat == SampleType::Float) - { - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else - { - sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], - static_cast(writable*mBytesPerBlock))}; - if(numbytes < 1) break; - read_bytes = static_cast(numbytes); + /* Note that the ring buffer's writable space is one byte + * less than the available area because the write offset + * ending up at the read offset would be interpreted as + * being empty instead of full. + */ + return roffset - woffset - 1; } - woffset += read_bytes; - } - else - { /* If the read offset is at or behind the write offset, the * writeable area (might) wrap around. Make sure the sample * data can fit, and calculate how much can go in front before * wrapping. */ - const size_t writable{(!roffset ? mBufferData.size()-woffset-1 : - (mBufferData.size()-woffset)) / mBytesPerBlock}; - if(!writable) break; + return mBufferData.size() - (!roffset ? woffset+1 : woffset); + }; - if(mSampleFormat == SampleType::Int16) - { - sf_count_t num_frames{sf_readf_short(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else if(mSampleFormat == SampleType::Float) - { - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable*mSamplesPerBlock))}; - if(num_frames < 1) break; - read_bytes = static_cast(num_frames) * mBytesPerBlock; - } - else - { - sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], - static_cast(writable*mBytesPerBlock))}; - if(numbytes < 1) break; - read_bytes = static_cast(numbytes); - } + const auto writable = get_writable() / mBytesPerBlock; + if(!writable) break; - woffset += read_bytes; - if(woffset == mBufferData.size()) - woffset = 0; + auto read_bytes = size_t{}; + if(mSampleFormat == SampleType::Int16) + { + const auto num_frames = sf_readf_short(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock)); + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; } + else if(mSampleFormat == SampleType::Float) + { + const auto num_frames = sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock)); + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else + { + const auto numbytes = sf_read_raw(mSndfile, &mBufferData[woffset], + static_cast(writable*mBytesPerBlock)); + if(numbytes < 1) break; + read_bytes = static_cast(numbytes); + } + + woffset += read_bytes; + if(woffset == mBufferData.size()) + woffset = 0; + mWritePos.store(woffset, std::memory_order_release); mDecoderOffset += read_bytes; } @@ -458,16 +445,16 @@ struct StreamPlayer { * ring buffer is empty, it's done, otherwise play the source with * what's available. */ - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - - roffset}; + const auto roffset = mReadPos.load(std::memory_order_relaxed); + const auto readable = ((woffset < roffset) ? mBufferData.size()+woffset : woffset) - + roffset; if(readable == 0) return false; /* Store the playback offset that the source will start reading * from, so it can be tracked during playback. */ - mStartOffset = mDecoderOffset - readable; + mStartOffset = (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock; alSourcePlay(mSource); if(alGetError() != AL_NO_ERROR) return false; @@ -476,33 +463,30 @@ struct StreamPlayer { } }; -} // namespace - -int main(int argc, char **argv) +int main(al::span args) { - /* A simple RAII container for OpenAL startup and shutdown. */ - struct AudioManager { - AudioManager(char ***argv_, int *argc_) - { - if(InitAL(argv_, argc_) != 0) - throw std::runtime_error{"Failed to initialize OpenAL"}; - } - ~AudioManager() { CloseAL(); } - }; - /* Print out usage if no arguments were specified */ - if(argc < 2) + if(args.size() < 2) { - fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); + fmt::println(stderr, "Usage: {} [-device ] ", args[0]); return 1; } + args = args.subspan(1); - argv++; argc--; - AudioManager almgr{&argv, &argc}; + if(InitAL(args) != 0) + throw std::runtime_error{"Failed to initialize OpenAL"}; + /* A simple RAII container for automating OpenAL shutdown. */ + struct AudioManager { + AudioManager() = default; + AudioManager(const AudioManager&) = delete; + auto operator=(const AudioManager&) -> AudioManager& = delete; + ~AudioManager() { CloseAL(); } + }; + AudioManager almgr; if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) { - fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n"); + fmt::println(stderr, "AL_SOFT_callback_buffer extension not available"); return 1; } @@ -512,23 +496,19 @@ int main(int argc, char **argv) ALCint refresh{25}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); - std::unique_ptr player{new StreamPlayer{}}; + auto player = std::make_unique(); /* Play each file listed on the command line */ - for(int i{0};i < argc;++i) + for(size_t i{0};i < args.size();++i) { - if(!player->open(argv[i])) + if(!player->open(std::string{args[i]})) continue; /* Get the name portion, without the path, for display. */ - const char *namepart{strrchr(argv[i], '/')}; - if(!namepart) namepart = strrchr(argv[i], '\\'); - if(namepart) - ++namepart; - else - namepart = argv[i]; + const auto fpos = std::max(args[i].rfind('/')+1, args[i].rfind('\\')+1); + const auto namepart = args[i].substr(fpos); - printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat), + fmt::println("Playing: {} ({}, {}hz)", namepart, FormatName(player->mFormat), player->mSfInfo.samplerate); fflush(stdout); @@ -546,7 +526,17 @@ int main(int argc, char **argv) player->close(); } /* All done. */ - printf("Done.\n"); + fmt::println("Done."); return 0; } + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/libs/openal-soft/examples/altonegen.c b/libs/openal-soft/examples/altonegen.c index 52a57875..c171e576 100644 --- a/libs/openal-soft/examples/altonegen.c +++ b/libs/openal-soft/examples/altonegen.c @@ -185,9 +185,9 @@ int main(int argc, char *argv[]) const char *appname = argv[0]; ALuint source, buffer; ALint last_pos; - ALint seconds = 4; + ALuint seconds = 4; ALint srate = -1; - ALint tone_freq = 1000; + ALuint tone_freq = 1000; ALCint dev_rate; ALenum state; ALfloat gain = 1.0f; @@ -229,9 +229,10 @@ int main(int argc, char *argv[]) if(i+1 < argc && strcmp(argv[i], "-t") == 0) { + char *endptr = NULL; i++; - seconds = atoi(argv[i]); - if(seconds <= 0) + seconds = (ALuint)strtoul(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || seconds <= 0) seconds = 4; } else if(i+1 < argc && (strcmp(argv[i], "--waveform") == 0 || strcmp(argv[i], "-w") == 0)) @@ -254,9 +255,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--freq") == 0 || strcmp(argv[i], "-f") == 0)) { + char *endptr = NULL; i++; - tone_freq = atoi(argv[i]); - if(tone_freq < 1) + tone_freq = (ALuint)strtoul(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || tone_freq < 1) { fprintf(stderr, "Invalid tone frequency: %s (min: 1hz)\n", argv[i]); tone_freq = 1; @@ -264,9 +266,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--gain") == 0 || strcmp(argv[i], "-g") == 0)) { + char *endptr = NULL; i++; - gain = (ALfloat)atof(argv[i]); - if(gain < 0.0f || gain > 1.0f) + gain = strtof(argv[i], &endptr); + if(!endptr || *endptr != '\0' || gain < 0.0f || gain > 1.0f) { fprintf(stderr, "Invalid gain: %s (min: 0.0, max 1.0)\n", argv[i]); gain = 1.0f; @@ -274,9 +277,10 @@ int main(int argc, char *argv[]) } else if(i+1 < argc && (strcmp(argv[i], "--srate") == 0 || strcmp(argv[i], "-s") == 0)) { + char *endptr = NULL; i++; - srate = atoi(argv[i]); - if(srate < 40) + srate = (ALint)strtol(argv[i], &endptr, 0); + if(!endptr || *endptr != '\0' || srate < 40) { fprintf(stderr, "Invalid sample rate: %s (min: 40hz)\n", argv[i]); srate = 40; @@ -293,15 +297,15 @@ int main(int argc, char *argv[]) srate = dev_rate; /* Load the sound into a buffer. */ - buffer = CreateWave(wavetype, (ALuint)seconds, (ALuint)tone_freq, (ALuint)srate, gain); + buffer = CreateWave(wavetype, seconds, tone_freq, (ALuint)srate, gain); if(!buffer) { CloseAL(); return 1; } - printf("Playing %dhz %s-wave tone with %dhz sample rate and %dhz output, for %d second%s...\n", - tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); + printf("Playing %uhz %s-wave tone with %dhz sample rate and %dhz output, for %u second%s...\n", + tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); fflush(stdout); /* Create the source to play the sound with. */ @@ -322,7 +326,7 @@ int main(int argc, char *argv[]) if(pos > last_pos) { - printf("%d...\n", seconds - pos); + printf("%u...\n", seconds - (ALuint)pos); fflush(stdout); } last_pos = pos; diff --git a/libs/openal-soft/examples/common/alhelpers.h b/libs/openal-soft/examples/common/alhelpers.h index 1a56e6dc..e8e0b277 100644 --- a/libs/openal-soft/examples/common/alhelpers.h +++ b/libs/openal-soft/examples/common/alhelpers.h @@ -2,6 +2,7 @@ #define ALHELPERS_H #include "AL/al.h" +#include "AL/alc.h" #ifdef __cplusplus extern "C" { @@ -23,7 +24,7 @@ void al_nssleep(unsigned long nsec); * still needs to use a normal cast and live with the warning (C++ is fine with * a regular reinterpret_cast). */ -#if __STDC_VERSION__ >= 199901L +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define FUNCTION_CAST(T, ptr) (union{void *p; T f;}){ptr}.f #elif defined(__cplusplus) #define FUNCTION_CAST(T, ptr) reinterpret_cast(ptr) @@ -33,6 +34,54 @@ void al_nssleep(unsigned long nsec); #ifdef __cplusplus } // extern "C" + +#include +#include +#include + +#include "alspan.h" +#include "fmt/core.h" + +int InitAL(al::span &args) +{ + ALCdevice *device{}; + + /* Open and initialize a device */ + if(args.size() > 1 && args[0] == "-device") + { + device = alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); + args = args.subspan(2); + } + if(!device) + device = alcOpenDevice(nullptr); + if(!device) + { + fmt::println(stderr, "Could not open a device!"); + return 1; + } + + ALCcontext *ctx{alcCreateContext(device, nullptr)}; + if(!ctx || alcMakeContextCurrent(ctx) == ALC_FALSE) + { + if(ctx) + alcDestroyContext(ctx); + alcCloseDevice(device); + fmt::println(stderr, "Could not set a context!"); + return 1; + } + + const ALCchar *name{}; + if(alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT")) + name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if(!name || alcGetError(device) != AL_NO_ERROR) + name = alcGetString(device, ALC_DEVICE_SPECIFIER); + fmt::println("Opened \"{}\"", name); + + return 0; +} + #endif #endif /* ALHELPERS_H */ diff --git a/libs/openal-soft/fmt-11.1.1/.clang-format b/libs/openal-soft/fmt-11.1.1/.clang-format new file mode 100644 index 00000000..31f8c343 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.clang-format @@ -0,0 +1,14 @@ +# Run manually to reformat a file: +# clang-format -i --style=file +Language: Cpp +BasedOnStyle: Google +IndentPPDirectives: AfterHash +IndentCaseLabels: false +AlwaysBreakTemplateDeclarations: false +DerivePointerAlignment: false +AllowShortCaseLabelsOnASingleLine: true +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: false \ No newline at end of file diff --git a/libs/openal-soft/fmt-11.1.1/.github/dependabot.yml b/libs/openal-soft/fmt-11.1.1/.github/dependabot.yml new file mode 100644 index 00000000..f74783d9 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # Necessary to update action hashes. + directory: "/" + schedule: + interval: "monthly" + # Allow up to 3 opened pull requests for github-actions versions. + open-pull-requests-limit: 3 diff --git a/libs/openal-soft/fmt-11.1.1/.github/issue_template.md b/libs/openal-soft/fmt-11.1.1/.github/issue_template.md new file mode 100644 index 00000000..8e39c5ba --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/issue_template.md @@ -0,0 +1,6 @@ + diff --git a/libs/openal-soft/fmt-11.1.1/.github/pull_request_template.md b/libs/openal-soft/fmt-11.1.1/.github/pull_request_template.md new file mode 100644 index 00000000..e8774133 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/pull_request_template.md @@ -0,0 +1,7 @@ + diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..50182ee9 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/cifuzz.yml @@ -0,0 +1,32 @@ +name: CIFuzz +on: [pull_request] + +permissions: + contents: read + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a + with: + oss-fuzz-project-name: 'fmt' + dry-run: false + language: c++ + + - name: Run fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a + with: + oss-fuzz-project-name: 'fmt' + fuzz-seconds: 300 + dry-run: false + language: c++ + + - name: Upload crash + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/doc.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/doc.yml new file mode 100644 index 00000000..019a85f1 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/doc.yml @@ -0,0 +1,43 @@ +name: doc + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + # Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken. + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Add Ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list + + - name: Create build environment + run: | + sudo apt update + sudo apt install doxygen + pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1 + cmake -E make_directory ${{runner.workspace}}/build + # Workaround https://github.com/actions/checkout/issues/13: + git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" + git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" + + - name: Build + working-directory: ${{runner.workspace}}/build + run: $GITHUB_WORKSPACE/support/mkdocs deploy dev + + - name: Deploy + env: + KEY: "${{secrets.KEY}}" + if: env.KEY != '' && github.ref == 'refs/heads/master' + working-directory: ${{runner.workspace}}/fmt/build/fmt.dev + run: git push https://$KEY@github.com/fmtlib/fmt.dev.git diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/lint.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/lint.yml new file mode 100644 index 00000000..cbef56a4 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: lint + +on: + pull_request: + paths: + - '**.h' + - '**.cc' + +permissions: + contents: read + +jobs: + format_code: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Install clang-format + run: | + wget https://apt.llvm.org/llvm.sh + sudo bash ./llvm.sh 17 + sudo apt install clang-format-17 + + - name: Run clang-format + run: | + find include src -name '*.h' -o -name '*.cc' | \ + xargs clang-format-17 -i -style=file -fallback-style=none + git diff --exit-code diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/linux.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/linux.yml new file mode 100644 index 00000000..97150467 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/linux.yml @@ -0,0 +1,118 @@ +name: linux + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + cxx: [g++-4.9, g++-10, clang++-9] + build_type: [Debug, Release] + std: [11] + shared: [""] + include: + - cxx: g++-4.9 + install: sudo apt install g++-4.9 + - cxx: g++-8 + build_type: Debug + std: 14 + install: sudo apt install g++-8 + - cxx: g++-8 + build_type: Debug + std: 17 + install: sudo apt install g++-8 + - cxx: g++-9 + build_type: Debug + std: 17 + - cxx: g++-10 + build_type: Debug + std: 17 + - cxx: g++-11 + build_type: Debug + std: 20 + install: sudo apt install g++-11 + - cxx: clang++-8 + build_type: Debug + std: 17 + cxxflags: -stdlib=libc++ + install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev + - cxx: clang++-9 + install: sudo apt install clang-9 + - cxx: clang++-9 + build_type: Debug + fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON + std: 17 + install: sudo apt install clang-9 + - cxx: clang++-11 + build_type: Debug + std: 20 + - cxx: clang++-11 + build_type: Debug + std: 20 + cxxflags: -stdlib=libc++ + install: sudo apt install libc++-11-dev libc++abi-11-dev + - cxx: g++-13 + build_type: Release + std: 23 + install: sudo apt install g++-13 + shared: -DBUILD_SHARED_LIBS=ON + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' + + - name: Add repositories for older GCC + run: | + # Below repo provides GCC 4.9. + sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial main' + sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe' + if: ${{ matrix.cxx == 'g++-4.9' }} + + - name: Add repositories for newer GCC + run: | + sudo apt-add-repository ppa:ubuntu-toolchain-r/test + if: ${{ matrix.cxx == 'g++-11' || matrix.cxx == 'g++-13' }} + + - name: Add Ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list + + - name: Create Build Environment + run: | + sudo apt update + ${{matrix.install}} + sudo apt install locales-all + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + env: + CXX: ${{matrix.cxx}} + CXXFLAGS: ${{matrix.cxxflags}} + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`nproc` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/macos.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/macos.yml new file mode 100644 index 00000000..3543ef57 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/macos.yml @@ -0,0 +1,58 @@ +name: macos + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [macos-13, macos-14] + build_type: [Debug, Release] + std: [11, 17, 20] + shared: [""] + exclude: + - { os: macos-13, std: 11 } + - { os: macos-13, std: 17 } + include: + - os: macos-14 + std: 23 + build_type: Release + shared: -DBUILD_SHARED_LIBS=ON + + runs-on: '${{ matrix.os }}' + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' + + - name: Select Xcode 14.3 (macOS 13) + run: sudo xcode-select -s "/Applications/Xcode_14.3.app" + if: ${{ matrix.os == 'macos-13' }} + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`sysctl -n hw.logicalcpu` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml new file mode 100644 index 00000000..b363a6f8 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/scorecard.yml @@ -0,0 +1,65 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '26 14 * * 5' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + with: + sarif_file: results.sarif diff --git a/libs/openal-soft/fmt-11.1.1/.github/workflows/windows.yml b/libs/openal-soft/fmt-11.1.1/.github/workflows/windows.yml new file mode 100644 index 00000000..5403e652 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.github/workflows/windows.yml @@ -0,0 +1,95 @@ +name: windows + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + # windows-2019 has MSVC 2019 installed; + # windows-2022 has MSVC 2022 installed: + # https://github.com/actions/virtual-environments. + os: [windows-2019] + platform: [Win32, x64] + toolset: [v141, v142] + standard: [14, 17, 20] + shared: ["", -DBUILD_SHARED_LIBS=ON] + build_type: [Debug, Release] + exclude: + - { toolset: v141, standard: 20 } + - { toolset: v142, standard: 14 } + - { platform: Win32, toolset: v141 } + - { platform: Win32, standard: 14 } + - { platform: Win32, standard: 20 } + - { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON } + include: + - os: windows-2022 + platform: x64 + toolset: v143 + build_type: Debug + standard: 20 + + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + # Use a bash shell for $GITHUB_WORKSPACE. + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \ + -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ + ${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} -V + env: + CTEST_OUTPUT_ON_FAILURE: True + + mingw: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + strategy: + matrix: + sys: [ mingw64, ucrt64 ] + steps: + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" + shell: cmd + - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 + with: + release: false + msystem: ${{matrix.sys}} + pacboy: cc:p cmake:p ninja:p lld:p + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Configure + run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug + env: { LDFLAGS: -fuse-ld=lld } + - name: Build + run: cmake --build ../build + - name: Test + run: ctest -j `nproc` --test-dir ../build + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/libs/openal-soft/fmt-11.1.1/.gitignore b/libs/openal-soft/fmt-11.1.1/.gitignore new file mode 100644 index 00000000..2635026a --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/.gitignore @@ -0,0 +1,20 @@ +*.a +*.so* +*.xcodeproj +*~ +.vscode/ +/CMakeScripts +/Testing +/_CPack_Packages +/install_manifest.txt +CMakeCache.txt +CMakeFiles +CPack*.cmake +CTestTestfile.cmake +FMT.build +Makefile +bin/ +build/ +cmake_install.cmake +fmt-*.cmake +fmt.pc diff --git a/libs/openal-soft/fmt-11.1.1/CMakeLists.txt b/libs/openal-soft/fmt-11.1.1/CMakeLists.txt new file mode 100644 index 00000000..acaf99e6 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.8...3.28) + +# [CR] Heavily modified for OpenAL Soft to avoid clashes if used as a sub- +# project with other uses of fmt. + +# Fallback for using newer policies on CMake <3.12. +if (${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif () + +# Joins arguments and places the results in ${result_var}. +function(join result_var) + set(result "") + foreach (arg ${ARGN}) + set(result "${result}${arg}") + endforeach () + set(${result_var} "${result}" PARENT_SCOPE) +endfunction() + +include(CMakeParseArguments) + +project(ALSOFT_FMT CXX) + +# Get version from base.h +file(READ include/fmt/base.h base_h) +if (NOT base_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") + message(FATAL_ERROR "Cannot get FMT_VERSION from base.h.") +endif () +# Use math to skip leading zeros if any. +math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) +math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) +math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) +join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}. + ${CPACK_PACKAGE_VERSION_PATCH}) +message(STATUS "{fmt} version: ${FMT_VERSION}") + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") + +include(CheckCXXCompilerFlag) +include(JoinPaths) + +function(add_headers VAR) + set(headers ${${VAR}}) + foreach (header ${ARGN}) + set(headers ${headers} include/fmt/${header}) + endforeach() + set(${VAR} ${headers} PARENT_SCOPE) +endfunction() + +set(ALSOFT_FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") + +# Define the fmt library, its includes and the needed defines. +add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h + format-inl.h os.h ostream.h printf.h ranges.h std.h + xchar.h) +set(FMT_SOURCES src/format.cc src/os.cc) + +add_library(alsoft.fmt OBJECT ${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md) +add_library(alsoft::fmt ALIAS alsoft.fmt) + +if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + target_compile_features(alsoft.fmt PUBLIC cxx_std_11) +else () + message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler") +endif () + +target_include_directories(alsoft.fmt PUBLIC + $) + +set_target_properties(alsoft.fmt PROPERTIES ${ALSOFT_STD_VERSION_PROPS} + VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${ALSOFT_FMT_DEBUG_POSTFIX}" + POSITION_INDEPENDENT_CODE TRUE + C_VISIBILITY_PRESET hidden + CXX_VISIBILITY_PRESET hidden + EXCLUDE_FROM_ALL TRUE) + +if (MSVC) + # Unicode support requires compiling with /utf-8. + target_compile_options(alsoft.fmt PUBLIC $<$:/utf-8>) +endif () diff --git a/libs/openal-soft/fmt-11.1.1/CONTRIBUTING.md b/libs/openal-soft/fmt-11.1.1/CONTRIBUTING.md new file mode 100644 index 00000000..b82f1450 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/CONTRIBUTING.md @@ -0,0 +1,20 @@ +Contributing to {fmt} +===================== + +By submitting a pull request or a patch, you represent that you have the right +to license your contribution to the {fmt} project owners and the community, +agree that your contributions are licensed under the {fmt} license, and agree +to future changes to the licensing. + +All C++ code must adhere to [Google C++ Style Guide]( +https://google.github.io/styleguide/cppguide.html) with the following +exceptions: + +* Exceptions are permitted +* snake_case should be used instead of UpperCamelCase for function and type + names + +All documentation must adhere to the [Google Developer Documentation Style +Guide](https://developers.google.com/style). + +Thanks for contributing! diff --git a/libs/openal-soft/fmt-11.1.1/ChangeLog.md b/libs/openal-soft/fmt-11.1.1/ChangeLog.md new file mode 100644 index 00000000..09ebaed6 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/ChangeLog.md @@ -0,0 +1,2866 @@ +# 11.1.1 - 2024-12-27 + +- Fixed ABI compatibility with earlier 11.x versions + (https://github.com/fmtlib/fmt/issues/4278). + +- Defined CMake components (`core` and `doc`) to allow docs to be installed + separately (https://github.com/fmtlib/fmt/pull/4276). + Thanks @carlsmedstad. + +# 11.1.0 - 2024-12-25 + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/issues/4081, + https://github.com/fmtlib/fmt/pull/4083, + https://github.com/fmtlib/fmt/pull/4084, + https://github.com/fmtlib/fmt/pull/4152, + https://github.com/fmtlib/fmt/issues/4153, + https://github.com/fmtlib/fmt/pull/4169, + https://github.com/fmtlib/fmt/issues/4190, + https://github.com/fmtlib/fmt/issues/4234, + https://github.com/fmtlib/fmt/pull/4239). + Thanks @kamrann and @Arghnews. + +- Reduced debug (unoptimized) binary code size and the number of template + instantiations when passing formatting arguments. For example, unoptimized + binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and + ~60% on clang (x86-64). + + GCC: + - Before: 161 instructions of which 105 are in reusable functions + ([godbolt](https://www.godbolt.org/z/s9bGoo4ze)). + - After: 116 instructions of which 60 are in reusable functions + ([godbolt](https://www.godbolt.org/z/r7GGGxMs6)). + + Clang: + - Before: 310 instructions of which 251 are in reusable functions + ([godbolt](https://www.godbolt.org/z/Ts88b7M9o)). + - After: 194 instructions of which 135 are in reusable functions + ([godbolt](https://www.godbolt.org/z/vcrjP8ceW)). + +- Added an experimental `fmt::writer` API that can be used for writing to + different destinations such as files or strings + (https://github.com/fmtlib/fmt/issues/2354). + For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)): + + ```c++ + #include + + void write_text(fmt::writer w) { + w.print("The answer is {}.", 42); + } + + int main() { + // Write to FILE. + write_text(stdout); + + // Write to fmt::ostream. + auto f = fmt::output_file("myfile"); + write_text(f); + + // Write to std::string. + auto sb = fmt::string_buffer(); + write_text(sb); + std::string s = sb.str(); + } + ``` + +- Added width and alignment support to the formatter of `std::error_code`. + +- Made `std::expected` formattable + (https://github.com/fmtlib/fmt/issues/4145, + https://github.com/fmtlib/fmt/pull/4148). + For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)): + + ```c++ + fmt::print("{}", std::expected()); + ``` + + prints + + ``` + expected() + ``` + + Thanks @phprus. + +- Made `fmt::is_formattable` SFINAE-friendly + (https://github.com/fmtlib/fmt/issues/4147). + +- Added support for `_BitInt` formatting when using clang + (https://github.com/fmtlib/fmt/issues/4007, + https://github.com/fmtlib/fmt/pull/4072, + https://github.com/fmtlib/fmt/issues/4140, + https://github.com/fmtlib/fmt/issues/4173, + https://github.com/fmtlib/fmt/pull/4176). + For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)): + + ```c++ + using int42 = _BitInt(42); + fmt::print("{}", int42(100)); + ``` + + Thanks @Arghnews. + +- Added the `n` specifier for tuples and pairs + (https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc. + +- Added support for tuple-like types to `fmt::join` + (https://github.com/fmtlib/fmt/issues/4226, + https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus. + +- Made more types formattable at compile time + (https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH. + +- Implemented a more efficient compile-time `fmt::formatted_size` + (https://github.com/fmtlib/fmt/issues/4102, + https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus. + +- Fixed compile-time formatting of some string types + (https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd. + +- Made compiled version of `fmt::format_to` work with + `std::back_insert_iterator>` + (https://github.com/fmtlib/fmt/issues/4206, + https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus. + +- Added a formatter for `std::reference_wrapper` + (https://github.com/fmtlib/fmt/pull/4163, + https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus. + +- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j` + and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH. + +- Made microseconds formatted as `us` instead of `µs` if the Unicode support is + disabled (https://github.com/fmtlib/fmt/issues/4088). + +- Fixed an unreleased regression in transcoding of surrogate pairs + (https://github.com/fmtlib/fmt/issues/4094, + https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus. + +- Made `fmt::appender` satisfy `std::output_iterator` concept + (https://github.com/fmtlib/fmt/issues/4092, + https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus. + +- Made `std::iterator_traits` standard-conforming + (https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter. + +- Made it easier to reuse `fmt::formatter` for types with + an implicit conversion to `std::string_view` + (https://github.com/fmtlib/fmt/issues/4036, + https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews. + +- Made it possible to disable `` use via `FMT_CPP_LIB_FILESYSTEM` + for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK + (https://github.com/fmtlib/fmt/issues/4257, + https://github.com/fmtlib/fmt/pull/4258, + https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus. + +- Fixed compatibility with platforms that use 80-bit `long double` + (https://github.com/fmtlib/fmt/issues/4245, + https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma. + +- Added support for UTF-32 code units greater than `0xFFFF` in fill + (https://github.com/fmtlib/fmt/issues/4201). + +- Fixed handling of legacy encodings on Windows with GCC + (https://github.com/fmtlib/fmt/issues/4162). + +- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference + (https://github.com/fmtlib/fmt/issues/4261, + https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel. + +- Added `fmt::dynamic_format_arg_store::size` + (https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch. + +- Removed the ability to control locale usage via an undocumented + `FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`. + +- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other + similar macros. + +- Improved include directory ordering to reduce the chance of including + incorrect headers when using multiple versions of {fmt} + (https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan. + +- Made it possible to compile a subset of {fmt} without the C++ runtime. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/pull/4066, + https://github.com/fmtlib/fmt/issues/4117, + https://github.com/fmtlib/fmt/issues/4203, + https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh. + +- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110, + https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado. + +- Improved CI (https://github.com/fmtlib/fmt/pull/4155, + https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2708, + https://github.com/fmtlib/fmt/issues/4091, + https://github.com/fmtlib/fmt/issues/4109, + https://github.com/fmtlib/fmt/issues/4113, + https://github.com/fmtlib/fmt/issues/4125, + https://github.com/fmtlib/fmt/issues/4129, + https://github.com/fmtlib/fmt/pull/4130, + https://github.com/fmtlib/fmt/pull/4131, + https://github.com/fmtlib/fmt/pull/4132, + https://github.com/fmtlib/fmt/issues/4133, + https://github.com/fmtlib/fmt/issues/4144, + https://github.com/fmtlib/fmt/issues/4150, + https://github.com/fmtlib/fmt/issues/4158, + https://github.com/fmtlib/fmt/pull/4159, + https://github.com/fmtlib/fmt/issues/4160, + https://github.com/fmtlib/fmt/pull/4170, + https://github.com/fmtlib/fmt/issues/4177, + https://github.com/fmtlib/fmt/pull/4187, + https://github.com/fmtlib/fmt/pull/4188, + https://github.com/fmtlib/fmt/pull/4194, + https://github.com/fmtlib/fmt/pull/4200, + https://github.com/fmtlib/fmt/issues/4205, + https://github.com/fmtlib/fmt/issues/4207, + https://github.com/fmtlib/fmt/pull/4208, + https://github.com/fmtlib/fmt/pull/4210, + https://github.com/fmtlib/fmt/issues/4220, + https://github.com/fmtlib/fmt/issues/4231, + https://github.com/fmtlib/fmt/issues/4232, + https://github.com/fmtlib/fmt/pull/4233, + https://github.com/fmtlib/fmt/pull/4236, + https://github.com/fmtlib/fmt/pull/4267, + https://github.com/fmtlib/fmt/pull/4271). + Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega, + @c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao. + +# 11.0.2 - 2024-07-20 + +- Fixed compatibility with non-POSIX systems + (https://github.com/fmtlib/fmt/issues/4054, + https://github.com/fmtlib/fmt/issues/4060). + +- Fixed performance regressions when using `std::back_insert_iterator` with + `fmt::format_to` (https://github.com/fmtlib/fmt/issues/4070). + +- Fixed handling of `std::generator` and move-only iterators + (https://github.com/fmtlib/fmt/issues/4053, + https://github.com/fmtlib/fmt/pull/4057). Thanks @Arghnews. + +- Made `formatter::parse` work with types convertible to + `std::string_view` (https://github.com/fmtlib/fmt/issues/4036, + https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews. + +- Made `volatile void*` formattable + (https://github.com/fmtlib/fmt/issues/4049, + https://github.com/fmtlib/fmt/pull/4056). Thanks @Arghnews. + +- Made `Glib::ustring` not be confused with `std::string` + (https://github.com/fmtlib/fmt/issues/4052). + +- Made `fmt::context` iterator compatible with STL algorithms that rely on + iterator category (https://github.com/fmtlib/fmt/issues/4079). + +# 11.0.1 - 2024-07-05 + +- Fixed version number in the inline namespace + (https://github.com/fmtlib/fmt/issues/4047). + +- Fixed disabling Unicode support via CMake + (https://github.com/fmtlib/fmt/issues/4051). + +- Fixed deprecated `visit_format_arg` (https://github.com/fmtlib/fmt/pull/4043). + Thanks @nebkat. + +- Fixed handling of a sign and improved the `std::complex` formater + (https://github.com/fmtlib/fmt/pull/4034, + https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus. + +- Fixed ADL issues in `fmt::printf` when using C++20 + (https://github.com/fmtlib/fmt/pull/4042). Thanks @toge. + +- Removed a redundant check in the formatter for `std::expected` + (https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus. + +# 11.0.0 - 2024-07-01 + +- Added `fmt/base.h` which provides a subset of the API with minimal include + dependencies and enough functionality to replace all uses of the `printf` + family of functions. This brings the compile time of code using {fmt} much + closer to the equivalent `printf` code as shown on the following benchmark + that compiles 100 source files: + + | Method | Compile Time (s) | + |--------------|------------------| + | printf | 1.6 | + | IOStreams | 25.9 | + | fmt 10.x | 19.0 | + | fmt 11.0 | 4.8 | + | tinyformat | 29.1 | + | Boost Format | 55.0 | + + This gives almost 4x improvement in build speed compared to version 10. + Note that the benchmark is purely formatting code and includes. In real + projects the difference from `printf` will be smaller partly because common + standard headers will be included in almost any translation unit (TU) anyway. + In particular, in every case except `printf` above ~1s is spent in total on + including `` in all TUs. + +- Optimized includes in other headers such as `fmt/format.h` which is now + roughly equivalent to the old `fmt/core.h` in terms of build speed. + +- Migrated the documentation at https://fmt.dev/ from Sphinx to MkDocs. + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/issues/3990, + https://github.com/fmtlib/fmt/pull/3991, + https://github.com/fmtlib/fmt/issues/3993, + https://github.com/fmtlib/fmt/pull/3994, + https://github.com/fmtlib/fmt/pull/3997, + https://github.com/fmtlib/fmt/pull/3998, + https://github.com/fmtlib/fmt/pull/4004, + https://github.com/fmtlib/fmt/pull/4005, + https://github.com/fmtlib/fmt/pull/4006, + https://github.com/fmtlib/fmt/pull/4013, + https://github.com/fmtlib/fmt/pull/4027, + https://github.com/fmtlib/fmt/pull/4029). In particular, native CMake support + for modules is now used if available. Thanks @yujincheng08 and @matt77hias. + +- Added an option to replace standard includes with `import std` enabled via + the `FMT_IMPORT_STD` macro (https://github.com/fmtlib/fmt/issues/3921, + https://github.com/fmtlib/fmt/pull/3928). Thanks @matt77hias. + +- Exported `fmt::range_format`, `fmt::range_format_kind` and + `fmt::compiled_string` from the `fmt` module + (https://github.com/fmtlib/fmt/pull/3970, + https://github.com/fmtlib/fmt/pull/3999). + Thanks @matt77hias and @yujincheng08. + +- Improved integration with stdio in `fmt::print`, enabling direct writes + into a C stream buffer in common cases. This may give significant + performance improvements ranging from tens of percent to [2x]( + https://stackoverflow.com/a/78457454/471164) and eliminates dynamic memory + allocations on the buffer level. It is currently enabled for built-in and + string types with wider availability coming up in future releases. + + For example, it gives ~24% improvement on a [simple benchmark]( + https://isocpp.org/files/papers/P3107R5.html#perf) compiled with Apple clang + version 15.0.0 (clang-1500.1.0.2.5) and run on macOS 14.2.1: + + ``` + ------------------------------------------------------- + Benchmark Time CPU Iterations + ------------------------------------------------------- + printf 81.8 ns 81.5 ns 8496899 + fmt::print (10.x) 63.8 ns 61.9 ns 11524151 + fmt::print (11.0) 51.3 ns 51.0 ns 13846580 + ``` + +- Improved safety of `fmt::format_to` when writing to an array + (https://github.com/fmtlib/fmt/pull/3805). + For example ([godbolt](https://www.godbolt.org/z/cYrn8dWY8)): + + ```c++ + auto volkswagen = char[4]; + auto result = fmt::format_to(volkswagen, "elephant"); + ``` + + no longer results in a buffer overflow. Instead the output will be truncated + and you can get the end iterator and whether truncation occurred from the + `result` object. Thanks @ThePhD. + +- Enabled Unicode support by default in MSVC, bringing it on par with other + compilers and making it unnecessary for users to enable it explicitly. + Most of {fmt} is encoding-agnostic but this prevents mojibake in places + where encoding matters such as path formatting and terminal output. + You can control the Unicode support via the CMake `FMT_UNICODE` option. + Note that some {fmt} packages such as the one in vcpkg have already been + compiled with Unicode enabled. + +- Added a formatter for `std::expected` + (https://github.com/fmtlib/fmt/pull/3834). Thanks @dominicpoeschko. + +- Added a formatter for `std::complex` + (https://github.com/fmtlib/fmt/issues/1467, + https://github.com/fmtlib/fmt/issues/3886, + https://github.com/fmtlib/fmt/pull/3892, + https://github.com/fmtlib/fmt/pull/3900). Thanks @phprus. + +- Added a formatter for `std::type_info` + (https://github.com/fmtlib/fmt/pull/3978). Thanks @matt77hias. + +- Specialized `formatter` for `std::basic_string` types with custom traits + and allocators (https://github.com/fmtlib/fmt/issues/3938, + https://github.com/fmtlib/fmt/pull/3943). Thanks @dieram3. + +- Added formatters for `std::chrono::day`, `std::chrono::month`, + `std::chrono::year` and `std::chrono::year_month_day` + (https://github.com/fmtlib/fmt/issues/3758, + https://github.com/fmtlib/fmt/issues/3772, + https://github.com/fmtlib/fmt/pull/3906, + https://github.com/fmtlib/fmt/pull/3913). For example: + + ```c++ + #include + #include + + int main() { + fmt::print(fg(fmt::color::green), "{}\n", std::chrono::day(7)); + } + ``` + + prints a green day: + + image + + Thanks @zivshek. + +- Fixed handling of precision in `%S` (https://github.com/fmtlib/fmt/issues/3794, + https://github.com/fmtlib/fmt/pull/3814). Thanks @js324. + +- Added support for the `-` specifier (glibc `strftime` extension) to day of + the month (`%d`) and week of the year (`%W`, `%U`, `%V`) specifiers + (https://github.com/fmtlib/fmt/pull/3976). Thanks @ZaheenJ. + +- Fixed the scope of the `-` extension in chrono formatting so that it doesn't + apply to subsequent specifiers (https://github.com/fmtlib/fmt/issues/3811, + https://github.com/fmtlib/fmt/pull/3812). Thanks @phprus. + +- Improved handling of `time_point::min()` + (https://github.com/fmtlib/fmt/issues/3282). + +- Added support for character range formatting + (https://github.com/fmtlib/fmt/issues/3857, + https://github.com/fmtlib/fmt/pull/3863). Thanks @js324. + +- Added `string` and `debug_string` range formatters + (https://github.com/fmtlib/fmt/pull/3973, + https://github.com/fmtlib/fmt/pull/4024). Thanks @matt77hias. + +- Enabled ADL for `begin` and `end` in `fmt::join` + (https://github.com/fmtlib/fmt/issues/3813, + https://github.com/fmtlib/fmt/pull/3824). Thanks @bbolli. + +- Made contiguous iterator optimizations apply to `std::basic_string` iterators + (https://github.com/fmtlib/fmt/pull/3798). Thanks @phprus. + +- Added support for ranges with mutable `begin` and `end` + (https://github.com/fmtlib/fmt/issues/3752, + https://github.com/fmtlib/fmt/pull/3800, + https://github.com/fmtlib/fmt/pull/3955). Thanks @tcbrindle and @Arghnews. + +- Added support for move-only iterators to `fmt::join` + (https://github.com/fmtlib/fmt/issues/3802, + https://github.com/fmtlib/fmt/pull/3946). Thanks @Arghnews. + +- Moved range and iterator overloads of `fmt::join` to `fmt/ranges.h`, next + to other overloads. + +- Fixed handling of types with `begin` returning `void` such as Eigen matrices + (https://github.com/fmtlib/fmt/issues/3839, + https://github.com/fmtlib/fmt/pull/3964). Thanks @Arghnews. + +- Added an `fmt::formattable` concept (https://github.com/fmtlib/fmt/pull/3974). + Thanks @matt77hias. + +- Added support for `__float128` (https://github.com/fmtlib/fmt/issues/3494). + +- Fixed rounding issues when formatting `long double` with fixed precision + (https://github.com/fmtlib/fmt/issues/3539). + +- Made `fmt::isnan` not trigger floating-point exception for NaN values + (https://github.com/fmtlib/fmt/issues/3948, + https://github.com/fmtlib/fmt/pull/3951). Thanks @alexdewar. + +- Removed dependency on `` for `std::allocator_traits` when possible + (https://github.com/fmtlib/fmt/pull/3804). Thanks @phprus. + +- Enabled compile-time checks in formatting functions that take text colors and + styles. + +- Deprecated wide stream overloads of `fmt::print` that take text styles. + +- Made format string compilation work with clang 12 and later despite + only partial non-type template parameter support + (https://github.com/fmtlib/fmt/issues/4000, + https://github.com/fmtlib/fmt/pull/4001). Thanks @yujincheng08. + +- Made `fmt::iterator_buffer`'s move constructor `noexcept` + (https://github.com/fmtlib/fmt/pull/3808). Thanks @waywardmonkeys. + +- Started enforcing that `formatter::format` is const for compatibility + with `std::format` (https://github.com/fmtlib/fmt/issues/3447). + +- Added `fmt::basic_format_arg::visit` and deprecated `fmt::visit_format_arg`. + +- Made `fmt::basic_string_view` not constructible from `nullptr` for + consistency with `std::string_view` in C++23 + (https://github.com/fmtlib/fmt/pull/3846). Thanks @dalle. + +- Fixed `fmt::group_digits` for negative integers + (https://github.com/fmtlib/fmt/issues/3891, + https://github.com/fmtlib/fmt/pull/3901). Thanks @phprus. + +- Fixed handling of negative ids in `fmt::basic_format_args::get` + (https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota. + +- Fixed handling of a buffer boundary on flush + (https://github.com/fmtlib/fmt/issues/4229). + +- Improved named argument validation + (https://github.com/fmtlib/fmt/issues/3817). + +- Disabled copy construction/assignment for `fmt::format_arg_store` and + fixed moved construction (https://github.com/fmtlib/fmt/pull/3833). + Thanks @ivafanas. + +- Worked around a locale issue in RHEL/devtoolset + (https://github.com/fmtlib/fmt/issues/3858, + https://github.com/fmtlib/fmt/pull/3859). Thanks @g199209. + +- Added RTTI detection for MSVC (https://github.com/fmtlib/fmt/pull/3821, + https://github.com/fmtlib/fmt/pull/3963). Thanks @edo9300. + +- Migrated the documentation from Sphinx to MkDocs. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/issues/3775, + https://github.com/fmtlib/fmt/pull/3784, + https://github.com/fmtlib/fmt/issues/3788, + https://github.com/fmtlib/fmt/pull/3789, + https://github.com/fmtlib/fmt/pull/3793, + https://github.com/fmtlib/fmt/issues/3818, + https://github.com/fmtlib/fmt/pull/3820, + https://github.com/fmtlib/fmt/pull/3822, + https://github.com/fmtlib/fmt/pull/3843, + https://github.com/fmtlib/fmt/pull/3890, + https://github.com/fmtlib/fmt/issues/3894, + https://github.com/fmtlib/fmt/pull/3895, + https://github.com/fmtlib/fmt/pull/3905, + https://github.com/fmtlib/fmt/issues/3942, + https://github.com/fmtlib/fmt/pull/4008). + Thanks @zencatalyst, WolleTD, @tupaschoal, @Dobiasd, @frank-weinberg, @bbolli, + @phprus, @waywardmonkeys, @js324 and @tchaikov. + +- Improved CI and tests + (https://github.com/fmtlib/fmt/issues/3878, + https://github.com/fmtlib/fmt/pull/3883, + https://github.com/fmtlib/fmt/issues/3897, + https://github.com/fmtlib/fmt/pull/3979, + https://github.com/fmtlib/fmt/pull/3980, + https://github.com/fmtlib/fmt/pull/3988, + https://github.com/fmtlib/fmt/pull/4010, + https://github.com/fmtlib/fmt/pull/4012, + https://github.com/fmtlib/fmt/pull/4038). + Thanks @vgorrX, @waywardmonkeys, @tchaikov and @phprus. + +- Fixed buffer overflow when using format string compilation with debug format + and `std::back_insert_iterator` (https://github.com/fmtlib/fmt/issues/3795, + https://github.com/fmtlib/fmt/pull/3797). Thanks @phprus. + +- Improved Bazel support + (https://github.com/fmtlib/fmt/pull/3792, + https://github.com/fmtlib/fmt/pull/3801, + https://github.com/fmtlib/fmt/pull/3962, + https://github.com/fmtlib/fmt/pull/3965). Thanks @Vertexwahn. + +- Improved/fixed the CMake config + (https://github.com/fmtlib/fmt/issues/3777, + https://github.com/fmtlib/fmt/pull/3783, + https://github.com/fmtlib/fmt/issues/3847, + https://github.com/fmtlib/fmt/pull/3907). Thanks @phprus and @xTachyon. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/3685, + https://github.com/fmtlib/fmt/issues/3769, + https://github.com/fmtlib/fmt/issues/3796, + https://github.com/fmtlib/fmt/issues/3803, + https://github.com/fmtlib/fmt/pull/3806, + https://github.com/fmtlib/fmt/pull/3807, + https://github.com/fmtlib/fmt/issues/3809, + https://github.com/fmtlib/fmt/pull/3810, + https://github.com/fmtlib/fmt/issues/3830, + https://github.com/fmtlib/fmt/pull/3832, + https://github.com/fmtlib/fmt/issues/3835, + https://github.com/fmtlib/fmt/pull/3844, + https://github.com/fmtlib/fmt/issues/3854, + https://github.com/fmtlib/fmt/pull/3856, + https://github.com/fmtlib/fmt/pull/3865, + https://github.com/fmtlib/fmt/pull/3866, + https://github.com/fmtlib/fmt/pull/3880, + https://github.com/fmtlib/fmt/issues/3881, + https://github.com/fmtlib/fmt/issues/3884, + https://github.com/fmtlib/fmt/issues/3898, + https://github.com/fmtlib/fmt/pull/3899, + https://github.com/fmtlib/fmt/pull/3909, + https://github.com/fmtlib/fmt/pull/3917, + https://github.com/fmtlib/fmt/pull/3923, + https://github.com/fmtlib/fmt/pull/3924, + https://github.com/fmtlib/fmt/issues/3925, + https://github.com/fmtlib/fmt/pull/3930, + https://github.com/fmtlib/fmt/pull/3931, + https://github.com/fmtlib/fmt/pull/3933, + https://github.com/fmtlib/fmt/issues/3935, + https://github.com/fmtlib/fmt/pull/3937, + https://github.com/fmtlib/fmt/pull/3967, + https://github.com/fmtlib/fmt/pull/3968, + https://github.com/fmtlib/fmt/pull/3972, + https://github.com/fmtlib/fmt/pull/3983, + https://github.com/fmtlib/fmt/issues/3992, + https://github.com/fmtlib/fmt/pull/3995, + https://github.com/fmtlib/fmt/pull/4009, + https://github.com/fmtlib/fmt/pull/4023). + Thanks @hmbj, @phprus, @res2k, @Baardi, @matt77hias, @waywardmonkeys, @hmbj, + @yakra, @prlw1, @Arghnews, @mtillmann0, @ShifftC, @eepp, @jimmy-park and + @ChristianGebhardt. + +# 10.2.1 - 2024-01-04 + +- Fixed ABI compatibility with earlier 10.x versions + (https://github.com/fmtlib/fmt/issues/3785, + https://github.com/fmtlib/fmt/pull/3786). Thanks @saraedum. + +# 10.2.0 - 2024-01-01 + +- Added support for the `%j` specifier (the number of days) for + `std::chrono::duration` (https://github.com/fmtlib/fmt/issues/3643, + https://github.com/fmtlib/fmt/pull/3732). Thanks @intelfx. + +- Added support for the chrono suffix for days and changed + the suffix for minutes from "m" to the correct "min" + (https://github.com/fmtlib/fmt/issues/3662, + https://github.com/fmtlib/fmt/pull/3664). + For example ([godbolt](https://godbolt.org/z/9KhMnq9ba)): + + ```c++ + #include + + int main() { + fmt::print("{}\n", std::chrono::days(42)); // prints "42d" + } + ``` + + Thanks @Richardk2n. + +- Fixed an overflow in `std::chrono::time_point` formatting with large dates + (https://github.com/fmtlib/fmt/issues/3725, + https://github.com/fmtlib/fmt/pull/3727). Thanks @cschreib. + +- Added a formatter for `std::source_location` + (https://github.com/fmtlib/fmt/pull/3730). + For example ([godbolt](https://godbolt.org/z/YajfKjhhr)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}\n", std::source_location::current()); + } + ``` + + prints + + ``` + /app/example.cpp:5:51: int main() + ``` + + Thanks @felix642. + +- Added a formatter for `std::bitset` + (https://github.com/fmtlib/fmt/pull/3660). + For example ([godbolt](https://godbolt.org/z/bdEaGeYxe)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}\n", std::bitset<6>(42)); // prints "101010" + } + ``` + + Thanks @muggenhor. + +- Added an experimental `nested_formatter` that provides an easy way of + applying a formatter to one or more subobjects while automatically handling + width, fill and alignment. For example: + + ```c++ + #include + + struct point { + double x, y; + }; + + template <> + struct fmt::formatter : nested_formatter { + auto format(point p, format_context& ctx) const { + return write_padded(ctx, [=](auto out) { + return format_to(out, "({}, {})", nested(p.x), nested(p.y)); + }); + } + }; + + int main() { + fmt::print("[{:>20.2f}]", point{1, 2}); + } + ``` + + prints + + ``` + [ (1.00, 2.00)] + ``` + +- Added the generic representation (`g`) to `std::filesystem::path` + (https://github.com/fmtlib/fmt/issues/3715, + https://github.com/fmtlib/fmt/pull/3729). For example: + + ```c++ + #include + #include + + int main() { + fmt::print("{:g}\n", std::filesystem::path("C:\\foo")); + } + ``` + + prints `"C:/foo"` on Windows. + + Thanks @js324. + +- Made `format_as` work with references + (https://github.com/fmtlib/fmt/pull/3739). Thanks @tchaikov. + +- Fixed formatting of invalid UTF-8 with precision + (https://github.com/fmtlib/fmt/issues/3284). + +- Fixed an inconsistency between `fmt::to_string` and `fmt::format` + (https://github.com/fmtlib/fmt/issues/3684). + +- Disallowed unsafe uses of `fmt::styled` + (https://github.com/fmtlib/fmt/issues/3625): + + ```c++ + auto s = fmt::styled(std::string("dangle"), fmt::emphasis::bold); + fmt::print("{}\n", s); // compile error + ``` + + Pass `fmt::styled(...)` as a parameter instead. + +- Added a null check when formatting a C string with the `s` specifier + (https://github.com/fmtlib/fmt/issues/3706). + +- Disallowed the `c` specifier for `bool` + (https://github.com/fmtlib/fmt/issues/3726, + https://github.com/fmtlib/fmt/pull/3734). Thanks @js324. + +- Made the default formatting unlocalized in `fmt::ostream_formatter` for + consistency with the rest of the library + (https://github.com/fmtlib/fmt/issues/3460). + +- Fixed localized formatting in bases other than decimal + (https://github.com/fmtlib/fmt/issues/3693, + https://github.com/fmtlib/fmt/pull/3750). Thanks @js324. + +- Fixed a performance regression in experimental `fmt::ostream::print` + (https://github.com/fmtlib/fmt/issues/3674). + +- Added synchronization with the underlying output stream when writing to + the Windows console + (https://github.com/fmtlib/fmt/pull/3668, + https://github.com/fmtlib/fmt/issues/3688, + https://github.com/fmtlib/fmt/pull/3689). + Thanks @Roman-Koshelev and @dimztimz. + +- Changed to only export `format_error` when {fmt} is built as a shared + library (https://github.com/fmtlib/fmt/issues/3626, + https://github.com/fmtlib/fmt/pull/3627). Thanks @phprus. + +- Made `fmt::streamed` `constexpr`. + (https://github.com/fmtlib/fmt/pull/3650). Thanks @muggenhor. + +- Made `fmt::format_int` `constexpr` + (https://github.com/fmtlib/fmt/issues/4031, + https://github.com/fmtlib/fmt/pull/4032). Thanks @dixlorenz. + +- Enabled `consteval` on older versions of MSVC + (https://github.com/fmtlib/fmt/pull/3757). Thanks @phprus. + +- Added an option to build without `wchar_t` support on Windows + (https://github.com/fmtlib/fmt/issues/3631, + https://github.com/fmtlib/fmt/pull/3636). Thanks @glebm. + +- Improved build and CI configuration + (https://github.com/fmtlib/fmt/pull/3679, + https://github.com/fmtlib/fmt/issues/3701, + https://github.com/fmtlib/fmt/pull/3702, + https://github.com/fmtlib/fmt/pull/3749). + Thanks @jcar87, @pklima and @tchaikov. + +- Fixed various warnings, compilation and test issues + (https://github.com/fmtlib/fmt/issues/3607, + https://github.com/fmtlib/fmt/pull/3610, + https://github.com/fmtlib/fmt/pull/3624, + https://github.com/fmtlib/fmt/pull/3630, + https://github.com/fmtlib/fmt/pull/3634, + https://github.com/fmtlib/fmt/pull/3638, + https://github.com/fmtlib/fmt/issues/3645, + https://github.com/fmtlib/fmt/issues/3646, + https://github.com/fmtlib/fmt/pull/3647, + https://github.com/fmtlib/fmt/pull/3652, + https://github.com/fmtlib/fmt/issues/3654, + https://github.com/fmtlib/fmt/pull/3663, + https://github.com/fmtlib/fmt/issues/3670, + https://github.com/fmtlib/fmt/pull/3680, + https://github.com/fmtlib/fmt/issues/3694, + https://github.com/fmtlib/fmt/pull/3695, + https://github.com/fmtlib/fmt/pull/3699, + https://github.com/fmtlib/fmt/issues/3705, + https://github.com/fmtlib/fmt/issues/3710, + https://github.com/fmtlib/fmt/issues/3712, + https://github.com/fmtlib/fmt/pull/3713, + https://github.com/fmtlib/fmt/issues/3714, + https://github.com/fmtlib/fmt/pull/3716, + https://github.com/fmtlib/fmt/pull/3723, + https://github.com/fmtlib/fmt/issues/3738, + https://github.com/fmtlib/fmt/issues/3740, + https://github.com/fmtlib/fmt/pull/3741, + https://github.com/fmtlib/fmt/pull/3743, + https://github.com/fmtlib/fmt/issues/3745, + https://github.com/fmtlib/fmt/pull/3747, + https://github.com/fmtlib/fmt/pull/3748, + https://github.com/fmtlib/fmt/pull/3751, + https://github.com/fmtlib/fmt/pull/3754, + https://github.com/fmtlib/fmt/pull/3755, + https://github.com/fmtlib/fmt/issues/3760, + https://github.com/fmtlib/fmt/pull/3762, + https://github.com/fmtlib/fmt/issues/3763, + https://github.com/fmtlib/fmt/pull/3764, + https://github.com/fmtlib/fmt/issues/3774, + https://github.com/fmtlib/fmt/pull/3779). + Thanks @danakj, @vinayyadav3016, @cyyever, @phprus, @qimiko, @saschasc, + @gsjaardema, @lazka, @Zhaojun-Liu, @carlsmedstad, @hotwatermorning, + @cptFracassa, @kuguma, @PeterJohnson, @H1X4Dev, @asantoni, @eltociear, + @msimberg, @tchaikov, @waywardmonkeys. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/issues/2086, + https://github.com/fmtlib/fmt/issues/3637, + https://github.com/fmtlib/fmt/pull/3642, + https://github.com/fmtlib/fmt/pull/3653, + https://github.com/fmtlib/fmt/pull/3655, + https://github.com/fmtlib/fmt/pull/3661, + https://github.com/fmtlib/fmt/issues/3673, + https://github.com/fmtlib/fmt/pull/3677, + https://github.com/fmtlib/fmt/pull/3737, + https://github.com/fmtlib/fmt/issues/3742, + https://github.com/fmtlib/fmt/pull/3744). + Thanks @idzm, @perlun, @joycebrum, @fennewald, @reinhardt1053, @GeorgeLS. + +- Updated CI dependencies + (https://github.com/fmtlib/fmt/pull/3615, + https://github.com/fmtlib/fmt/pull/3622, + https://github.com/fmtlib/fmt/pull/3623, + https://github.com/fmtlib/fmt/pull/3666, + https://github.com/fmtlib/fmt/pull/3696, + https://github.com/fmtlib/fmt/pull/3697, + https://github.com/fmtlib/fmt/pull/3759, + https://github.com/fmtlib/fmt/pull/3782). + +# 10.1.1 - 2023-08-28 + +- Added formatters for `std::atomic` and `atomic_flag` + (https://github.com/fmtlib/fmt/pull/3574, + https://github.com/fmtlib/fmt/pull/3594). + Thanks @wangzw and @AlexGuteniev. +- Fixed an error about partial specialization of `formatter` + after instantiation when compiled with gcc and C++20 + (https://github.com/fmtlib/fmt/issues/3584). +- Fixed compilation as a C++20 module with gcc and clang + (https://github.com/fmtlib/fmt/issues/3587, + https://github.com/fmtlib/fmt/pull/3597, + https://github.com/fmtlib/fmt/pull/3605). + Thanks @MathewBensonCode. +- Made `fmt::to_string` work with types that have `format_as` + overloads (https://github.com/fmtlib/fmt/pull/3575). Thanks @phprus. +- Made `formatted_size` work with integral format specifiers at + compile time (https://github.com/fmtlib/fmt/pull/3591). + Thanks @elbeno. +- Fixed a warning about the `no_unique_address` attribute on clang-cl + (https://github.com/fmtlib/fmt/pull/3599). Thanks @lukester1975. +- Improved compatibility with the legacy GBK encoding + (https://github.com/fmtlib/fmt/issues/3598, + https://github.com/fmtlib/fmt/pull/3599). Thanks @YuHuanTin. +- Added OpenSSF Scorecard analysis + (https://github.com/fmtlib/fmt/issues/3530, + https://github.com/fmtlib/fmt/pull/3571). Thanks @joycebrum. +- Updated CI dependencies + (https://github.com/fmtlib/fmt/pull/3591, + https://github.com/fmtlib/fmt/pull/3592, + https://github.com/fmtlib/fmt/pull/3593, + https://github.com/fmtlib/fmt/pull/3602). + +# 10.1.0 - 2023-08-12 + +- Optimized format string compilation resulting in up to 40% speed up + in compiled `format_to` and \~4x speed up in compiled `format_to_n` + on a concatenation benchmark + (https://github.com/fmtlib/fmt/issues/3133, + https://github.com/fmtlib/fmt/issues/3484). + + {fmt} 10.0: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 78.9 ns 78.9 ns 8881746 + BM_format_to_n 568 ns 568 ns 1232089 + + {fmt} 10.1: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 54.9 ns 54.9 ns 12727944 + BM_format_to_n 133 ns 133 ns 5257795 + +- Optimized storage of an empty allocator in `basic_memory_buffer` + (https://github.com/fmtlib/fmt/pull/3485). Thanks @Minty-Meeo. + +- Added formatters for proxy references to elements of + `std::vector` and `std::bitset` + (https://github.com/fmtlib/fmt/issues/3567, + https://github.com/fmtlib/fmt/pull/3570). For example + ([godbolt](https://godbolt.org/z/zYb79Pvn8)): + + ```c++ + #include + #include + + int main() { + auto v = std::vector{true}; + fmt::print("{}", v[0]); + } + ``` + + Thanks @phprus and @felix642. + +- Fixed an ambiguous formatter specialization for containers that look + like container adaptors such as `boost::flat_set` + (https://github.com/fmtlib/fmt/issues/3556, + https://github.com/fmtlib/fmt/pull/3561). Thanks @5chmidti. + +- Fixed compilation when formatting durations not convertible from + `std::chrono::seconds` + (https://github.com/fmtlib/fmt/pull/3430). Thanks @patlkli. + +- Made the `formatter` specialization for `char*` const-correct + (https://github.com/fmtlib/fmt/pull/3432). Thanks @timsong-cpp. + +- Made `{}` and `{:}` handled consistently during compile-time checks + (https://github.com/fmtlib/fmt/issues/3526). + +- Disallowed passing temporaries to `make_format_args` to improve API + safety by preventing dangling references. + +- Improved the compile-time error for unformattable types + (https://github.com/fmtlib/fmt/pull/3478). Thanks @BRevzin. + +- Improved the floating-point formatter + (https://github.com/fmtlib/fmt/pull/3448, + https://github.com/fmtlib/fmt/pull/3450). + Thanks @florimond-collette. + +- Fixed handling of precision for `long double` larger than 64 bits. + (https://github.com/fmtlib/fmt/issues/3539, + https://github.com/fmtlib/fmt/issues/3564). + +- Made floating-point and chrono tests less platform-dependent + (https://github.com/fmtlib/fmt/issues/3337, + https://github.com/fmtlib/fmt/issues/3433, + https://github.com/fmtlib/fmt/pull/3434). Thanks @phprus. + +- Removed the remnants of the Grisu floating-point formatter that has + been replaced by Dragonbox in earlier versions. + +- Added `throw_format_error` to the public API + (https://github.com/fmtlib/fmt/pull/3551). Thanks @mjerabek. + +- Made `FMT_THROW` assert even if assertions are disabled when + compiling with exceptions disabled + (https://github.com/fmtlib/fmt/issues/3418, + https://github.com/fmtlib/fmt/pull/3439). Thanks @BRevzin. + +- Made `format_as` and `std::filesystem::path` formatter work with + exotic code unit types. + (https://github.com/fmtlib/fmt/pull/3457, + https://github.com/fmtlib/fmt/pull/3476). Thanks @gix and @hmbj. + +- Added support for the `?` format specifier to + `std::filesystem::path` and made the default unescaped for + consistency with strings. + +- Deprecated the wide stream overload of `printf`. + +- Removed unused `basic_printf_parse_context`. + +- Improved RTTI detection used when formatting exceptions + (https://github.com/fmtlib/fmt/pull/3468). Thanks @danakj. + +- Improved compatibility with VxWorks7 + (https://github.com/fmtlib/fmt/pull/3467). Thanks @wenshan1. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/3174, + https://github.com/fmtlib/fmt/issues/3423, + https://github.com/fmtlib/fmt/pull/3454, + https://github.com/fmtlib/fmt/issues/3458, + https://github.com/fmtlib/fmt/pull/3461, + https://github.com/fmtlib/fmt/issues/3487, + https://github.com/fmtlib/fmt/pull/3515). + Thanks @zencatalyst, @rlalik and @mikecrowe. + +- Improved build and CI configurations + (https://github.com/fmtlib/fmt/issues/3449, + https://github.com/fmtlib/fmt/pull/3451, + https://github.com/fmtlib/fmt/pull/3452, + https://github.com/fmtlib/fmt/pull/3453, + https://github.com/fmtlib/fmt/pull/3459, + https://github.com/fmtlib/fmt/issues/3481, + https://github.com/fmtlib/fmt/pull/3486, + https://github.com/fmtlib/fmt/issues/3489, + https://github.com/fmtlib/fmt/pull/3496, + https://github.com/fmtlib/fmt/issues/3517, + https://github.com/fmtlib/fmt/pull/3523, + https://github.com/fmtlib/fmt/pull/3563). + Thanks @joycebrum, @glebm, @phprus, @petrmanek, @setoye and @abouvier. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/3408, + https://github.com/fmtlib/fmt/issues/3424, + https://github.com/fmtlib/fmt/issues/3444, + https://github.com/fmtlib/fmt/pull/3446, + https://github.com/fmtlib/fmt/pull/3475, + https://github.com/fmtlib/fmt/pull/3482, + https://github.com/fmtlib/fmt/issues/3492, + https://github.com/fmtlib/fmt/pull/3493, + https://github.com/fmtlib/fmt/pull/3508, + https://github.com/fmtlib/fmt/issues/3509, + https://github.com/fmtlib/fmt/issues/3533, + https://github.com/fmtlib/fmt/pull/3542, + https://github.com/fmtlib/fmt/issues/3543, + https://github.com/fmtlib/fmt/issues/3540, + https://github.com/fmtlib/fmt/pull/3544, + https://github.com/fmtlib/fmt/issues/3548, + https://github.com/fmtlib/fmt/pull/3549, + https://github.com/fmtlib/fmt/pull/3550, + https://github.com/fmtlib/fmt/pull/3552). + Thanks @adesitter, @hmbj, @Minty-Meeo, @phprus, @TobiSchluter, + @kieranclancy, @alexeedm, @jurihock, @Ozomahtli and @razaqq. + +# 10.0.0 - 2023-05-09 + +- Replaced Grisu with a new floating-point formatting algorithm for + given precision (https://github.com/fmtlib/fmt/issues/3262, + https://github.com/fmtlib/fmt/issues/2750, + https://github.com/fmtlib/fmt/pull/3269, + https://github.com/fmtlib/fmt/pull/3276). The new algorithm + is based on Dragonbox already used for the shortest representation + and gives substantial performance improvement: + + ![](https://user-images.githubusercontent.com/33922675/211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png) + + - Red: new algorithm + - Green: new algorithm with `FMT_USE_FULL_CACHE_DRAGONBOX` defined + to 1 + - Blue: old algorithm + + Thanks @jk-jeon. + +- Replaced `snprintf`-based hex float formatter with an internal + implementation (https://github.com/fmtlib/fmt/pull/3179, + https://github.com/fmtlib/fmt/pull/3203). This removes the + last usage of `s(n)printf` in {fmt}. Thanks @phprus. + +- Fixed alignment of floating-point numbers with localization + (https://github.com/fmtlib/fmt/issues/3263, + https://github.com/fmtlib/fmt/pull/3272). Thanks @ShawnZhong. + +- Made handling of `#` consistent with `std::format`. + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/pull/3134, + https://github.com/fmtlib/fmt/pull/3254, + https://github.com/fmtlib/fmt/pull/3386, + https://github.com/fmtlib/fmt/pull/3387, + https://github.com/fmtlib/fmt/pull/3388, + https://github.com/fmtlib/fmt/pull/3392, + https://github.com/fmtlib/fmt/pull/3397, + https://github.com/fmtlib/fmt/pull/3399, + https://github.com/fmtlib/fmt/pull/3400). + Thanks @laitingsheng, @Orvid and @DanielaE. + +- Switched to the [modules CMake library](https://github.com/vitaut/modules) + which allows building {fmt} as a C++20 module with clang: + + CXX=clang++ cmake -DFMT_MODULE=ON . + make + +- Made `format_as` work with any user-defined type and not just enums. + For example ([godbolt](https://godbolt.org/z/b7rqhq5Kh)): + + ```c++ + #include + + struct floaty_mc_floatface { + double value; + }; + + auto format_as(floaty_mc_floatface f) { return f.value; } + + int main() { + fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42" + } + ``` + +- Removed deprecated implicit conversions for enums and conversions to + primitive types for compatibility with `std::format` and to prevent + potential ODR violations. Use `format_as` instead. + +- Added support for fill, align and width to the time point formatter + (https://github.com/fmtlib/fmt/issues/3237, + https://github.com/fmtlib/fmt/pull/3260, + https://github.com/fmtlib/fmt/pull/3275). For example + ([godbolt](https://godbolt.org/z/rKP6MGz6c)): + + ```c++ + #include + + int main() { + // prints " 2023" + fmt::print("{:>8%Y}\n", std::chrono::system_clock::now()); + } + ``` + + Thanks @ShawnZhong. + +- Implemented formatting of subseconds + (https://github.com/fmtlib/fmt/issues/2207, + https://github.com/fmtlib/fmt/issues/3117, + https://github.com/fmtlib/fmt/pull/3115, + https://github.com/fmtlib/fmt/pull/3143, + https://github.com/fmtlib/fmt/pull/3144, + https://github.com/fmtlib/fmt/pull/3349). For example + ([godbolt](https://godbolt.org/z/45738oGEo)): + + ```c++ + #include + + int main() { + // prints 01.234567 + fmt::print("{:%S}\n", std::chrono::microseconds(1234567)); + } + ``` + + Thanks @patrickroocks @phprus and @BRevzin. + +- Added precision support to `%S` + (https://github.com/fmtlib/fmt/pull/3148). Thanks @SappyJoy + +- Added support for `std::utc_time` + (https://github.com/fmtlib/fmt/issues/3098, + https://github.com/fmtlib/fmt/pull/3110). Thanks @patrickroocks. + +- Switched formatting of `std::chrono::system_clock` from local time + to UTC for compatibility with the standard + (https://github.com/fmtlib/fmt/issues/3199, + https://github.com/fmtlib/fmt/pull/3230). Thanks @ned14. + +- Added support for `%Ez` and `%Oz` to chrono formatters. + (https://github.com/fmtlib/fmt/issues/3220, + https://github.com/fmtlib/fmt/pull/3222). Thanks @phprus. + +- Improved validation of format specifiers for `std::chrono::duration` + (https://github.com/fmtlib/fmt/issues/3219, + https://github.com/fmtlib/fmt/pull/3232). Thanks @ShawnZhong. + +- Fixed formatting of time points before the epoch + (https://github.com/fmtlib/fmt/issues/3117, + https://github.com/fmtlib/fmt/pull/3261). For example + ([godbolt](https://godbolt.org/z/f7bcznb3W)): + + ```c++ + #include + + int main() { + auto t = std::chrono::system_clock::from_time_t(0) - + std::chrono::milliseconds(250); + fmt::print("{:%S}\n", t); // prints 59.750000000 + } + ``` + + Thanks @ShawnZhong. + +- Experimental: implemented glibc extension for padding seconds, + minutes and hours + (https://github.com/fmtlib/fmt/issues/2959, + https://github.com/fmtlib/fmt/pull/3271). Thanks @ShawnZhong. + +- Added a formatter for `std::exception` + (https://github.com/fmtlib/fmt/issues/2977, + https://github.com/fmtlib/fmt/issues/3012, + https://github.com/fmtlib/fmt/pull/3062, + https://github.com/fmtlib/fmt/pull/3076, + https://github.com/fmtlib/fmt/pull/3119). For example + ([godbolt](https://godbolt.org/z/8xoWGs9e4)): + + ```c++ + #include + #include + + int main() { + try { + std::vector().at(0); + } catch(const std::exception& e) { + fmt::print("{}", e); + } + } + ``` + + prints: + + vector::_M_range_check: __n (which is 0) >= this->size() (which is 0) + + on libstdc++. Thanks @zach2good and @phprus. + +- Moved `std::error_code` formatter from `fmt/os.h` to `fmt/std.h`. + (https://github.com/fmtlib/fmt/pull/3125). Thanks @phprus. + +- Added formatters for standard container adapters: + `std::priority_queue`, `std::queue` and `std::stack` + (https://github.com/fmtlib/fmt/issues/3215, + https://github.com/fmtlib/fmt/pull/3279). For example + ([godbolt](https://godbolt.org/z/74h1xY9qK)): + + ```c++ + #include + #include + #include + + int main() { + auto s = std::stack>(); + for (auto b: {true, false, true}) s.push(b); + fmt::print("{}\n", s); // prints [true, false, true] + } + ``` + + Thanks @ShawnZhong. + +- Added a formatter for `std::optional` to `fmt/std.h` + (https://github.com/fmtlib/fmt/issues/1367, + https://github.com/fmtlib/fmt/pull/3303). + Thanks @tom-huntington. + +- Fixed formatting of valueless by exception variants + (https://github.com/fmtlib/fmt/pull/3347). Thanks @TheOmegaCarrot. + +- Made `fmt::ptr` accept `unique_ptr` with a custom deleter + (https://github.com/fmtlib/fmt/pull/3177). Thanks @hmbj. + +- Fixed formatting of noncopyable ranges and nested ranges of chars + (https://github.com/fmtlib/fmt/pull/3158 + https://github.com/fmtlib/fmt/issues/3286, + https://github.com/fmtlib/fmt/pull/3290). Thanks @BRevzin. + +- Fixed issues with formatting of paths and ranges of paths + (https://github.com/fmtlib/fmt/issues/3319, + https://github.com/fmtlib/fmt/pull/3321 + https://github.com/fmtlib/fmt/issues/3322). Thanks @phprus. + +- Improved handling of invalid Unicode in paths. + +- Enabled compile-time checks on Apple clang 14 and later + (https://github.com/fmtlib/fmt/pull/3331). Thanks @cloyce. + +- Improved compile-time checks of named arguments + (https://github.com/fmtlib/fmt/issues/3105, + https://github.com/fmtlib/fmt/pull/3214). Thanks @rbrich. + +- Fixed formatting when both alignment and `0` are given + (https://github.com/fmtlib/fmt/issues/3236, + https://github.com/fmtlib/fmt/pull/3248). Thanks @ShawnZhong. + +- Improved Unicode support in the experimental file API on Windows + (https://github.com/fmtlib/fmt/issues/3234, + https://github.com/fmtlib/fmt/pull/3293). Thanks @Fros1er. + +- Unified UTF transcoding + (https://github.com/fmtlib/fmt/pull/3416). Thanks @phprus. + +- Added support for UTF-8 digit separators via an experimental locale + facet (https://github.com/fmtlib/fmt/issues/1861). For + example ([godbolt](https://godbolt.org/z/f7bcznb3W)): + + ```c++ + auto loc = std::locale( + std::locale(), new fmt::format_facet("’")); + auto s = fmt::format(loc, "{:L}", 1000); + ``` + + where `’` is U+2019 used as a digit separator in the de_CH locale. + +- Added an overload of `formatted_size` that takes a locale + (https://github.com/fmtlib/fmt/issues/3084, + https://github.com/fmtlib/fmt/pull/3087). Thanks @gerboengels. + +- Removed the deprecated `FMT_DEPRECATED_OSTREAM`. + +- Fixed a UB when using a null `std::string_view` with + `fmt::to_string` or format string compilation + (https://github.com/fmtlib/fmt/issues/3241, + https://github.com/fmtlib/fmt/pull/3244). Thanks @phprus. + +- Added `starts_with` to the fallback `string_view` implementation + (https://github.com/fmtlib/fmt/pull/3080). Thanks @phprus. + +- Added `fmt::basic_format_string::get()` for compatibility with + `basic_format_string` + (https://github.com/fmtlib/fmt/pull/3111). Thanks @huangqinjin. + +- Added `println` for compatibility with C++23 + (https://github.com/fmtlib/fmt/pull/3267). Thanks @ShawnZhong. + +- Renamed the `FMT_EXPORT` macro for shared library usage to + `FMT_LIB_EXPORT`. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/3108, + https://github.com/fmtlib/fmt/issues/3169, + https://github.com/fmtlib/fmt/pull/3243). + https://github.com/fmtlib/fmt/pull/3404, + https://github.com/fmtlib/fmt/pull/4002). + Thanks @Cleroth, @Vertexwahn and @yujincheng08. + +- Improved build configuration and tests + (https://github.com/fmtlib/fmt/pull/3118, + https://github.com/fmtlib/fmt/pull/3120, + https://github.com/fmtlib/fmt/pull/3188, + https://github.com/fmtlib/fmt/issues/3189, + https://github.com/fmtlib/fmt/pull/3198, + https://github.com/fmtlib/fmt/pull/3205, + https://github.com/fmtlib/fmt/pull/3207, + https://github.com/fmtlib/fmt/pull/3210, + https://github.com/fmtlib/fmt/pull/3240, + https://github.com/fmtlib/fmt/pull/3256, + https://github.com/fmtlib/fmt/pull/3264, + https://github.com/fmtlib/fmt/issues/3299, + https://github.com/fmtlib/fmt/pull/3302, + https://github.com/fmtlib/fmt/pull/3312, + https://github.com/fmtlib/fmt/issues/3317, + https://github.com/fmtlib/fmt/pull/3328, + https://github.com/fmtlib/fmt/pull/3333, + https://github.com/fmtlib/fmt/pull/3369, + https://github.com/fmtlib/fmt/issues/3373, + https://github.com/fmtlib/fmt/pull/3395, + https://github.com/fmtlib/fmt/pull/3406, + https://github.com/fmtlib/fmt/pull/3411). + Thanks @dimztimz, @phprus, @DavidKorczynski, @ChrisThrasher, + @FrancoisCarouge, @kennyweiss, @luzpaz, @codeinred, @Mixaill, @joycebrum, + @kevinhwang and @Vertexwahn. + +- Fixed a regression in handling empty format specifiers after a colon + (`{:}`) (https://github.com/fmtlib/fmt/pull/3086). Thanks @oxidase. + +- Worked around a broken implementation of + `std::is_constant_evaluated` in some versions of libstdc++ on clang + (https://github.com/fmtlib/fmt/issues/3247, + https://github.com/fmtlib/fmt/pull/3281). Thanks @phprus. + +- Fixed formatting of volatile variables + (https://github.com/fmtlib/fmt/pull/3068). + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/3057, + https://github.com/fmtlib/fmt/pull/3066, + https://github.com/fmtlib/fmt/pull/3072, + https://github.com/fmtlib/fmt/pull/3082, + https://github.com/fmtlib/fmt/pull/3091, + https://github.com/fmtlib/fmt/issues/3092, + https://github.com/fmtlib/fmt/pull/3093, + https://github.com/fmtlib/fmt/pull/3095, + https://github.com/fmtlib/fmt/issues/3096, + https://github.com/fmtlib/fmt/pull/3097, + https://github.com/fmtlib/fmt/issues/3128, + https://github.com/fmtlib/fmt/pull/3129, + https://github.com/fmtlib/fmt/pull/3137, + https://github.com/fmtlib/fmt/pull/3139, + https://github.com/fmtlib/fmt/issues/3140, + https://github.com/fmtlib/fmt/pull/3142, + https://github.com/fmtlib/fmt/issues/3149, + https://github.com/fmtlib/fmt/pull/3150, + https://github.com/fmtlib/fmt/issues/3154, + https://github.com/fmtlib/fmt/issues/3163, + https://github.com/fmtlib/fmt/issues/3178, + https://github.com/fmtlib/fmt/pull/3184, + https://github.com/fmtlib/fmt/pull/3196, + https://github.com/fmtlib/fmt/issues/3204, + https://github.com/fmtlib/fmt/pull/3206, + https://github.com/fmtlib/fmt/pull/3208, + https://github.com/fmtlib/fmt/issues/3213, + https://github.com/fmtlib/fmt/pull/3216, + https://github.com/fmtlib/fmt/issues/3224, + https://github.com/fmtlib/fmt/issues/3226, + https://github.com/fmtlib/fmt/issues/3228, + https://github.com/fmtlib/fmt/pull/3229, + https://github.com/fmtlib/fmt/pull/3259, + https://github.com/fmtlib/fmt/issues/3274, + https://github.com/fmtlib/fmt/issues/3287, + https://github.com/fmtlib/fmt/pull/3288, + https://github.com/fmtlib/fmt/issues/3292, + https://github.com/fmtlib/fmt/pull/3295, + https://github.com/fmtlib/fmt/pull/3296, + https://github.com/fmtlib/fmt/issues/3298, + https://github.com/fmtlib/fmt/issues/3325, + https://github.com/fmtlib/fmt/pull/3326, + https://github.com/fmtlib/fmt/issues/3334, + https://github.com/fmtlib/fmt/issues/3342, + https://github.com/fmtlib/fmt/pull/3343, + https://github.com/fmtlib/fmt/issues/3351, + https://github.com/fmtlib/fmt/pull/3352, + https://github.com/fmtlib/fmt/pull/3362, + https://github.com/fmtlib/fmt/issues/3365, + https://github.com/fmtlib/fmt/pull/3366, + https://github.com/fmtlib/fmt/pull/3374, + https://github.com/fmtlib/fmt/issues/3377, + https://github.com/fmtlib/fmt/pull/3378, + https://github.com/fmtlib/fmt/issues/3381, + https://github.com/fmtlib/fmt/pull/3398, + https://github.com/fmtlib/fmt/pull/3413, + https://github.com/fmtlib/fmt/issues/3415). + Thanks @phprus, @gsjaardema, @NewbieOrange, @EngineLessCC, @asmaloney, + @HazardyKnusperkeks, @sergiud, @Youw, @thesmurph, @czudziakm, + @Roman-Koshelev, @chronoxor, @ShawnZhong, @russelltg, @glebm, @tmartin-gh, + @Zhaojun-Liu, @louiswins and @mogemimi. + +# 9.1.0 - 2022-08-27 + +- `fmt::formatted_size` now works at compile time + (https://github.com/fmtlib/fmt/pull/3026). For example + ([godbolt](https://godbolt.org/z/1MW5rMdf8)): + + ```c++ + #include + + int main() { + using namespace fmt::literals; + constexpr size_t n = fmt::formatted_size("{}"_cf, 42); + fmt::print("{}\n", n); // prints 2 + } + ``` + + Thanks @marksantaniello. + +- Fixed handling of invalid UTF-8 + (https://github.com/fmtlib/fmt/pull/3038, + https://github.com/fmtlib/fmt/pull/3044, + https://github.com/fmtlib/fmt/pull/3056). + Thanks @phprus and @skeeto. + +- Improved Unicode support in `ostream` overloads of `print` + (https://github.com/fmtlib/fmt/pull/2994, + https://github.com/fmtlib/fmt/pull/3001, + https://github.com/fmtlib/fmt/pull/3025). Thanks @dimztimz. + +- Fixed handling of the sign specifier in localized formatting on + systems with 32-bit `wchar_t` + (https://github.com/fmtlib/fmt/issues/3041). + +- Added support for wide streams to `fmt::streamed` + (https://github.com/fmtlib/fmt/pull/2994). Thanks @phprus. + +- Added the `n` specifier that disables the output of delimiters when + formatting ranges (https://github.com/fmtlib/fmt/pull/2981, + https://github.com/fmtlib/fmt/pull/2983). For example + ([godbolt](https://godbolt.org/z/roKqGdj8c)): + + ```c++ + #include + #include + + int main() { + auto v = std::vector{1, 2, 3}; + fmt::print("{:n}\n", v); // prints 1, 2, 3 + } + ``` + + Thanks @BRevzin. + +- Worked around problematic `std::string_view` constructors introduced + in C++23 (https://github.com/fmtlib/fmt/issues/3030, + https://github.com/fmtlib/fmt/issues/3050). Thanks @strega-nil-ms. + +- Improve handling (exclusion) of recursive ranges + (https://github.com/fmtlib/fmt/issues/2968, + https://github.com/fmtlib/fmt/pull/2974). Thanks @Dani-Hub. + +- Improved error reporting in format string compilation + (https://github.com/fmtlib/fmt/issues/3055). + +- Improved the implementation of + [Dragonbox](https://github.com/jk-jeon/dragonbox), the algorithm + used for the default floating-point formatting + (https://github.com/fmtlib/fmt/pull/2984). Thanks @jk-jeon. + +- Fixed issues with floating-point formatting on exotic platforms. + +- Improved the implementation of chrono formatting + (https://github.com/fmtlib/fmt/pull/3010). Thanks @phprus. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/2966, + https://github.com/fmtlib/fmt/pull/3009, + https://github.com/fmtlib/fmt/issues/3020, + https://github.com/fmtlib/fmt/pull/3037). + Thanks @mwinterb, @jcelerier and @remiburtin. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2991, + https://github.com/fmtlib/fmt/pull/2995, + https://github.com/fmtlib/fmt/issues/3004, + https://github.com/fmtlib/fmt/pull/3007, + https://github.com/fmtlib/fmt/pull/3040). + Thanks @dimztimz and @hwhsu1231. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2969, + https://github.com/fmtlib/fmt/pull/2971, + https://github.com/fmtlib/fmt/issues/2975, + https://github.com/fmtlib/fmt/pull/2982, + https://github.com/fmtlib/fmt/pull/2985, + https://github.com/fmtlib/fmt/issues/2988, + https://github.com/fmtlib/fmt/issues/2989, + https://github.com/fmtlib/fmt/issues/3000, + https://github.com/fmtlib/fmt/issues/3006, + https://github.com/fmtlib/fmt/issues/3014, + https://github.com/fmtlib/fmt/issues/3015, + https://github.com/fmtlib/fmt/pull/3021, + https://github.com/fmtlib/fmt/issues/3023, + https://github.com/fmtlib/fmt/pull/3024, + https://github.com/fmtlib/fmt/pull/3029, + https://github.com/fmtlib/fmt/pull/3043, + https://github.com/fmtlib/fmt/issues/3052, + https://github.com/fmtlib/fmt/pull/3053, + https://github.com/fmtlib/fmt/pull/3054). + Thanks @h-friederich, @dimztimz, @olupton, @bernhardmgruber and @phprus. + +# 9.0.0 - 2022-07-04 + +- Switched to the internal floating point formatter for all decimal + presentation formats. In particular this results in consistent + rounding on all platforms and removing the `s[n]printf` fallback for + decimal FP formatting. + +- Compile-time floating point formatting no longer requires the + header-only mode. For example + ([godbolt](https://godbolt.org/z/G37PTeG3b)): + + ```c++ + #include + #include + + consteval auto compile_time_dtoa(double value) -> std::array { + auto result = std::array(); + fmt::format_to(result.data(), FMT_COMPILE("{}"), value); + return result; + } + + constexpr auto answer = compile_time_dtoa(0.42); + ``` + + works with the default settings. + +- Improved the implementation of + [Dragonbox](https://github.com/jk-jeon/dragonbox), the algorithm + used for the default floating-point formatting + (https://github.com/fmtlib/fmt/pull/2713, + https://github.com/fmtlib/fmt/pull/2750). Thanks @jk-jeon. + +- Made `fmt::to_string` work with `__float128`. This uses the internal + FP formatter and works even on system without `__float128` support + in `[s]printf`. + +- Disabled automatic `std::ostream` insertion operator (`operator<<`) + discovery when `fmt/ostream.h` is included to prevent ODR + violations. You can get the old behavior by defining + `FMT_DEPRECATED_OSTREAM` but this will be removed in the next major + release. Use `fmt::streamed` or `fmt::ostream_formatter` to enable + formatting via `std::ostream` instead. + +- Added `fmt::ostream_formatter` that can be used to write `formatter` + specializations that perform formatting via `std::ostream`. For + example ([godbolt](https://godbolt.org/z/5sEc5qMsf)): + + ```c++ + #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" + ``` + +- Added the `fmt::streamed` function that takes an object and formats + it via `std::ostream`. For example + ([godbolt](https://godbolt.org/z/5G3346G1f)): + + ```c++ + #include + #include + + int main() { + fmt::print("Current thread id: {}\n", + fmt::streamed(std::this_thread::get_id())); + } + ``` + + Note that `fmt/std.h` provides a `formatter` specialization for + `std::thread::id` so you don\'t need to format it via + `std::ostream`. + +- Deprecated implicit conversions of unscoped enums to integers for + consistency with scoped enums. + +- Added an argument-dependent lookup based `format_as` extension API + to simplify formatting of enums. + +- Added experimental `std::variant` formatting support + (https://github.com/fmtlib/fmt/pull/2941). For example + ([godbolt](https://godbolt.org/z/KG9z6cq68)): + + ```c++ + #include + #include + + int main() { + auto v = std::variant(42); + fmt::print("{}\n", v); + } + ``` + + prints: + + variant(42) + + Thanks @jehelset. + +- Added experimental `std::filesystem::path` formatting support + (https://github.com/fmtlib/fmt/issues/2865, + https://github.com/fmtlib/fmt/pull/2902, + https://github.com/fmtlib/fmt/issues/2917, + https://github.com/fmtlib/fmt/pull/2918). For example + ([godbolt](https://godbolt.org/z/o44dMexEb)): + + ```c++ + #include + #include + + int main() { + fmt::print("There is no place like {}.", std::filesystem::path("/home")); + } + ``` + + prints: + + There is no place like "/home". + + Thanks @phprus. + +- Added a `std::thread::id` formatter to `fmt/std.h`. For example + ([godbolt](https://godbolt.org/z/j1azbYf3E)): + + ```c++ + #include + #include + + int main() { + fmt::print("Current thread id: {}\n", std::this_thread::get_id()); + } + ``` + +- Added `fmt::styled` that applies a text style to an individual + argument (https://github.com/fmtlib/fmt/pull/2793). For + example ([godbolt](https://godbolt.org/z/vWGW7v5M6)): + + ```c++ + #include + #include + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print( + "[{}] {}: {}\n", + fmt::styled(now, fmt::emphasis::bold), + fmt::styled("error", fg(fmt::color::red)), + "something went wrong"); + } + ``` + + prints + + ![](https://user-images.githubusercontent.com/576385/175071215-12809244-dab0-4005-96d8-7cd911c964d5.png) + + Thanks @rbrugo. + +- Made `fmt::print` overload for text styles correctly handle UTF-8 + (https://github.com/fmtlib/fmt/issues/2681, + https://github.com/fmtlib/fmt/pull/2701). Thanks @AlexGuteniev. + +- Fixed Unicode handling when writing to an ostream. + +- Added support for nested specifiers to range formatting + (https://github.com/fmtlib/fmt/pull/2673). For example + ([godbolt](https://godbolt.org/z/xd3Gj38cf)): + + ```c++ + #include + #include + + int main() { + fmt::print("{::#x}\n", std::vector{10, 20, 30}); + } + ``` + + prints `[0xa, 0x14, 0x1e]`. + + Thanks @BRevzin. + +- Implemented escaping of wide strings in ranges + (https://github.com/fmtlib/fmt/pull/2904). Thanks @phprus. + +- Added support for ranges with `begin` / `end` found via the + argument-dependent lookup + (https://github.com/fmtlib/fmt/pull/2807). Thanks @rbrugo. + +- Fixed formatting of certain kinds of ranges of ranges + (https://github.com/fmtlib/fmt/pull/2787). Thanks @BRevzin. + +- Fixed handling of maps with element types other than `std::pair` + (https://github.com/fmtlib/fmt/pull/2944). Thanks @BrukerJWD. + +- Made tuple formatter enabled only if elements are formattable + (https://github.com/fmtlib/fmt/issues/2939, + https://github.com/fmtlib/fmt/pull/2940). Thanks @jehelset. + +- Made `fmt::join` compatible with format string compilation + (https://github.com/fmtlib/fmt/issues/2719, + https://github.com/fmtlib/fmt/pull/2720). Thanks @phprus. + +- Made compile-time checks work with named arguments of custom types + and `std::ostream` `print` overloads + (https://github.com/fmtlib/fmt/issues/2816, + https://github.com/fmtlib/fmt/issues/2817, + https://github.com/fmtlib/fmt/pull/2819). Thanks @timsong-cpp. + +- Removed `make_args_checked` because it is no longer needed for + compile-time checks + (https://github.com/fmtlib/fmt/pull/2760). Thanks @phprus. + +- Removed the following deprecated APIs: `_format`, `arg_join`, the + `format_to` overload that takes a memory buffer, `[v]fprintf` that + takes an `ostream`. + +- Removed the deprecated implicit conversion of `[const] signed char*` + and `[const] unsigned char*` to C strings. + +- Removed the deprecated `fmt/locale.h`. + +- Replaced the deprecated `fileno()` with `descriptor()` in + `buffered_file`. + +- Moved `to_string_view` to the `detail` namespace since it\'s an + implementation detail. + +- Made access mode of a created file consistent with `fopen` by + setting `S_IWGRP` and `S_IWOTH` + (https://github.com/fmtlib/fmt/pull/2733). Thanks @arogge. + +- Removed a redundant buffer resize when formatting to `std::ostream` + (https://github.com/fmtlib/fmt/issues/2842, + https://github.com/fmtlib/fmt/pull/2843). Thanks @jcelerier. + +- Made precision computation for strings consistent with width + (https://github.com/fmtlib/fmt/issues/2888). + +- Fixed handling of locale separators in floating point formatting + (https://github.com/fmtlib/fmt/issues/2830). + +- Made sign specifiers work with `__int128_t` + (https://github.com/fmtlib/fmt/issues/2773). + +- Improved support for systems such as CHERI with extra data stored in + pointers (https://github.com/fmtlib/fmt/pull/2932). + Thanks @davidchisnall. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/2706, + https://github.com/fmtlib/fmt/pull/2712, + https://github.com/fmtlib/fmt/pull/2789, + https://github.com/fmtlib/fmt/pull/2803, + https://github.com/fmtlib/fmt/pull/2805, + https://github.com/fmtlib/fmt/pull/2815, + https://github.com/fmtlib/fmt/pull/2924). + Thanks @BRevzin, @Pokechu22, @setoye, @rtobar, @rbrugo, @anoonD and + @leha-bot. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2766, + https://github.com/fmtlib/fmt/pull/2772, + https://github.com/fmtlib/fmt/pull/2836, + https://github.com/fmtlib/fmt/pull/2852, + https://github.com/fmtlib/fmt/pull/2907, + https://github.com/fmtlib/fmt/pull/2913, + https://github.com/fmtlib/fmt/pull/2914). + Thanks @kambala-decapitator, @mattiasljungstrom, @kieselnb, @nathannaveen + and @Vertexwahn. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2408, + https://github.com/fmtlib/fmt/issues/2507, + https://github.com/fmtlib/fmt/issues/2697, + https://github.com/fmtlib/fmt/issues/2715, + https://github.com/fmtlib/fmt/issues/2717, + https://github.com/fmtlib/fmt/pull/2722, + https://github.com/fmtlib/fmt/pull/2724, + https://github.com/fmtlib/fmt/pull/2725, + https://github.com/fmtlib/fmt/issues/2726, + https://github.com/fmtlib/fmt/pull/2728, + https://github.com/fmtlib/fmt/pull/2732, + https://github.com/fmtlib/fmt/issues/2738, + https://github.com/fmtlib/fmt/pull/2742, + https://github.com/fmtlib/fmt/issues/2744, + https://github.com/fmtlib/fmt/issues/2745, + https://github.com/fmtlib/fmt/issues/2746, + https://github.com/fmtlib/fmt/issues/2754, + https://github.com/fmtlib/fmt/pull/2755, + https://github.com/fmtlib/fmt/issues/2757, + https://github.com/fmtlib/fmt/pull/2758, + https://github.com/fmtlib/fmt/issues/2761, + https://github.com/fmtlib/fmt/pull/2762, + https://github.com/fmtlib/fmt/issues/2763, + https://github.com/fmtlib/fmt/pull/2765, + https://github.com/fmtlib/fmt/issues/2769, + https://github.com/fmtlib/fmt/pull/2770, + https://github.com/fmtlib/fmt/issues/2771, + https://github.com/fmtlib/fmt/issues/2777, + https://github.com/fmtlib/fmt/pull/2779, + https://github.com/fmtlib/fmt/pull/2782, + https://github.com/fmtlib/fmt/pull/2783, + https://github.com/fmtlib/fmt/issues/2794, + https://github.com/fmtlib/fmt/issues/2796, + https://github.com/fmtlib/fmt/pull/2797, + https://github.com/fmtlib/fmt/pull/2801, + https://github.com/fmtlib/fmt/pull/2802, + https://github.com/fmtlib/fmt/issues/2808, + https://github.com/fmtlib/fmt/issues/2818, + https://github.com/fmtlib/fmt/pull/2819, + https://github.com/fmtlib/fmt/issues/2829, + https://github.com/fmtlib/fmt/issues/2835, + https://github.com/fmtlib/fmt/issues/2848, + https://github.com/fmtlib/fmt/issues/2860, + https://github.com/fmtlib/fmt/pull/2861, + https://github.com/fmtlib/fmt/pull/2882, + https://github.com/fmtlib/fmt/issues/2886, + https://github.com/fmtlib/fmt/issues/2891, + https://github.com/fmtlib/fmt/pull/2892, + https://github.com/fmtlib/fmt/issues/2895, + https://github.com/fmtlib/fmt/issues/2896, + https://github.com/fmtlib/fmt/pull/2903, + https://github.com/fmtlib/fmt/issues/2906, + https://github.com/fmtlib/fmt/issues/2908, + https://github.com/fmtlib/fmt/pull/2909, + https://github.com/fmtlib/fmt/issues/2920, + https://github.com/fmtlib/fmt/pull/2922, + https://github.com/fmtlib/fmt/pull/2927, + https://github.com/fmtlib/fmt/pull/2929, + https://github.com/fmtlib/fmt/issues/2936, + https://github.com/fmtlib/fmt/pull/2937, + https://github.com/fmtlib/fmt/pull/2938, + https://github.com/fmtlib/fmt/pull/2951, + https://github.com/fmtlib/fmt/issues/2954, + https://github.com/fmtlib/fmt/pull/2957, + https://github.com/fmtlib/fmt/issues/2958, + https://github.com/fmtlib/fmt/pull/2960). + Thanks @matrackif @Tobi823, @ivan-volnov, @VasiliPupkin256, + @federico-busato, @barcharcraz, @jk-jeon, @HazardyKnusperkeks, @dalboris, + @seanm, @gsjaardema, @timsong-cpp, @seanm, @frithrah, @chronoxor, @Agga, + @madmaxoft, @JurajX, @phprus and @Dani-Hub. + +# 8.1.1 - 2022-01-06 + +- Restored ABI compatibility with version 8.0.x + (https://github.com/fmtlib/fmt/issues/2695, + https://github.com/fmtlib/fmt/pull/2696). Thanks @saraedum. +- Fixed chrono formatting on big endian systems + (https://github.com/fmtlib/fmt/issues/2698, + https://github.com/fmtlib/fmt/pull/2699). + Thanks @phprus and @xvitaly. +- Fixed a linkage error with mingw + (https://github.com/fmtlib/fmt/issues/2691, + https://github.com/fmtlib/fmt/pull/2692). Thanks @rbberger. + +# 8.1.0 - 2022-01-02 + +- Optimized chrono formatting + (https://github.com/fmtlib/fmt/pull/2500, + https://github.com/fmtlib/fmt/pull/2537, + https://github.com/fmtlib/fmt/issues/2541, + https://github.com/fmtlib/fmt/pull/2544, + https://github.com/fmtlib/fmt/pull/2550, + https://github.com/fmtlib/fmt/pull/2551, + https://github.com/fmtlib/fmt/pull/2576, + https://github.com/fmtlib/fmt/issues/2577, + https://github.com/fmtlib/fmt/pull/2586, + https://github.com/fmtlib/fmt/pull/2591, + https://github.com/fmtlib/fmt/pull/2594, + https://github.com/fmtlib/fmt/pull/2602, + https://github.com/fmtlib/fmt/pull/2617, + https://github.com/fmtlib/fmt/issues/2628, + https://github.com/fmtlib/fmt/pull/2633, + https://github.com/fmtlib/fmt/issues/2670, + https://github.com/fmtlib/fmt/pull/2671). + + Processing of some specifiers such as `%z` and `%Y` is now up to + 10-20 times faster, for example on GCC 11 with libstdc++: + + ---------------------------------------------------------------------------- + Benchmark Before After + ---------------------------------------------------------------------------- + FMTFormatter_z 261 ns 26.3 ns + FMTFormatterCompile_z 246 ns 11.6 ns + FMTFormatter_Y 263 ns 26.1 ns + FMTFormatterCompile_Y 244 ns 10.5 ns + ---------------------------------------------------------------------------- + + Thanks @phprus and @toughengineer. + +- Implemented subsecond formatting for chrono durations + (https://github.com/fmtlib/fmt/pull/2623). For example + ([godbolt](https://godbolt.org/z/es7vWTETe)): + + ```c++ + #include + + int main() { + fmt::print("{:%S}", std::chrono::milliseconds(1234)); + } + ``` + + prints \"01.234\". + + Thanks @matrackif. + +- Fixed handling of precision 0 when formatting chrono durations + (https://github.com/fmtlib/fmt/issues/2587, + https://github.com/fmtlib/fmt/pull/2588). Thanks @lukester1975. + +- Fixed an overflow on invalid inputs in the `tm` formatter + (https://github.com/fmtlib/fmt/pull/2564). Thanks @phprus. + +- Added `fmt::group_digits` that formats integers with a non-localized + digit separator (comma) for groups of three digits. For example + ([godbolt](https://godbolt.org/z/TxGxG9Poq)): + + ```c++ + #include + + int main() { + fmt::print("{} dollars", fmt::group_digits(1000000)); + } + ``` + + prints \"1,000,000 dollars\". + +- Added support for faint, conceal, reverse and blink text styles + (https://github.com/fmtlib/fmt/pull/2394): + + + + Thanks @benit8 and @data-man. + +- Added experimental support for compile-time floating point + formatting (https://github.com/fmtlib/fmt/pull/2426, + https://github.com/fmtlib/fmt/pull/2470). It is currently + limited to the header-only mode. Thanks @alexezeder. + +- Added UDL-based named argument support to compile-time format string + checks (https://github.com/fmtlib/fmt/issues/2640, + https://github.com/fmtlib/fmt/pull/2649). For example + ([godbolt](https://godbolt.org/z/ohGbbvonv)): + + ```c++ + #include + + int main() { + using namespace fmt::literals; + fmt::print("{answer:s}", "answer"_a=42); + } + ``` + + gives a compile-time error on compilers with C++20 `consteval` and + non-type template parameter support (gcc 10+) because `s` is not a + valid format specifier for an integer. + + Thanks @alexezeder. + +- Implemented escaping of string range elements. For example + ([godbolt](https://godbolt.org/z/rKvM1vKf3)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}", std::vector{"\naan"}); + } + ``` + + is now printed as: + + ["\naan"] + + instead of: + + [" + aan"] + +- Added an experimental `?` specifier for escaping strings. + (https://github.com/fmtlib/fmt/pull/2674). Thanks @BRevzin. + +- Switched to JSON-like representation of maps and sets for + consistency with Python\'s `str.format`. For example + ([godbolt](https://godbolt.org/z/seKjoY9W5)): + + ```c++ + #include + #include + + int main() { + fmt::print("{}", std::map{{"answer", 42}}); + } + ``` + + is now printed as: + + {"answer": 42} + +- Extended `fmt::join` to support C++20-only ranges + (https://github.com/fmtlib/fmt/pull/2549). Thanks @BRevzin. + +- Optimized handling of non-const-iterable ranges and implemented + initial support for non-const-formattable types. + +- Disabled implicit conversions of scoped enums to integers that was + accidentally introduced in earlier versions + (https://github.com/fmtlib/fmt/pull/1841). + +- Deprecated implicit conversion of `[const] signed char*` and + `[const] unsigned char*` to C strings. + +- Deprecated `_format`, a legacy UDL-based format API + (https://github.com/fmtlib/fmt/pull/2646). Thanks @alexezeder. + +- Marked `format`, `formatted_size` and `to_string` as `[[nodiscard]]` + (https://github.com/fmtlib/fmt/pull/2612). @0x8000-0000. + +- Added missing diagnostic when trying to format function and member + pointers as well as objects convertible to pointers which is + explicitly disallowed + (https://github.com/fmtlib/fmt/issues/2598, + https://github.com/fmtlib/fmt/pull/2609, + https://github.com/fmtlib/fmt/pull/2610). Thanks @AlexGuteniev. + +- Optimized writing to a contiguous buffer with `format_to_n` + (https://github.com/fmtlib/fmt/pull/2489). Thanks @Roman-Koshelev. + +- Optimized writing to non-`char` buffers + (https://github.com/fmtlib/fmt/pull/2477). Thanks @Roman-Koshelev. + +- Decimal point is now localized when using the `L` specifier. + +- Improved floating point formatter implementation + (https://github.com/fmtlib/fmt/pull/2498, + https://github.com/fmtlib/fmt/pull/2499). Thanks @Roman-Koshelev. + +- Fixed handling of very large precision in fixed format + (https://github.com/fmtlib/fmt/pull/2616). + +- Made a table of cached powers used in FP formatting static + (https://github.com/fmtlib/fmt/pull/2509). Thanks @jk-jeon. + +- Resolved a lookup ambiguity with C++20 format-related functions due + to ADL (https://github.com/fmtlib/fmt/issues/2639, + https://github.com/fmtlib/fmt/pull/2641). Thanks @mkurdej. + +- Removed unnecessary inline namespace qualification + (https://github.com/fmtlib/fmt/issues/2642, + https://github.com/fmtlib/fmt/pull/2643). Thanks @mkurdej. + +- Implemented argument forwarding in `format_to_n` + (https://github.com/fmtlib/fmt/issues/2462, + https://github.com/fmtlib/fmt/pull/2463). Thanks @owent. + +- Fixed handling of implicit conversions in `fmt::to_string` and + format string compilation + (https://github.com/fmtlib/fmt/issues/2565). + +- Changed the default access mode of files created by + `fmt::output_file` to `-rw-r--r--` for consistency with `fopen` + (https://github.com/fmtlib/fmt/issues/2530). + +- Make `fmt::ostream::flush` public + (https://github.com/fmtlib/fmt/issues/2435). + +- Improved C++14/17 attribute detection + (https://github.com/fmtlib/fmt/pull/2615). Thanks @AlexGuteniev. + +- Improved `consteval` detection for MSVC + (https://github.com/fmtlib/fmt/pull/2559). Thanks @DanielaE. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/2406, + https://github.com/fmtlib/fmt/pull/2446, + https://github.com/fmtlib/fmt/issues/2493, + https://github.com/fmtlib/fmt/issues/2513, + https://github.com/fmtlib/fmt/pull/2515, + https://github.com/fmtlib/fmt/issues/2522, + https://github.com/fmtlib/fmt/pull/2562, + https://github.com/fmtlib/fmt/pull/2575, + https://github.com/fmtlib/fmt/pull/2606, + https://github.com/fmtlib/fmt/pull/2620, + https://github.com/fmtlib/fmt/issues/2676). + Thanks @sobolevn, @UnePierre, @zhsj, @phprus, @ericcurtin and @Lounarok. + +- Improved fuzzers and added a fuzzer for chrono timepoint formatting + (https://github.com/fmtlib/fmt/pull/2461, + https://github.com/fmtlib/fmt/pull/2469). @pauldreik, + +- Added the `FMT_SYSTEM_HEADERS` CMake option setting which marks + {fmt}\'s headers as system. It can be used to suppress warnings + (https://github.com/fmtlib/fmt/issues/2644, + https://github.com/fmtlib/fmt/pull/2651). Thanks @alexezeder. + +- Added the Bazel build system support + (https://github.com/fmtlib/fmt/pull/2505, + https://github.com/fmtlib/fmt/pull/2516). Thanks @Vertexwahn. + +- Improved build configuration and tests + (https://github.com/fmtlib/fmt/issues/2437, + https://github.com/fmtlib/fmt/pull/2558, + https://github.com/fmtlib/fmt/pull/2648, + https://github.com/fmtlib/fmt/pull/2650, + https://github.com/fmtlib/fmt/pull/2663, + https://github.com/fmtlib/fmt/pull/2677). + Thanks @DanielaE, @alexezeder and @phprus. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/2353, + https://github.com/fmtlib/fmt/pull/2356, + https://github.com/fmtlib/fmt/pull/2399, + https://github.com/fmtlib/fmt/issues/2408, + https://github.com/fmtlib/fmt/pull/2414, + https://github.com/fmtlib/fmt/pull/2427, + https://github.com/fmtlib/fmt/pull/2432, + https://github.com/fmtlib/fmt/pull/2442, + https://github.com/fmtlib/fmt/pull/2434, + https://github.com/fmtlib/fmt/issues/2439, + https://github.com/fmtlib/fmt/pull/2447, + https://github.com/fmtlib/fmt/pull/2450, + https://github.com/fmtlib/fmt/issues/2455, + https://github.com/fmtlib/fmt/issues/2465, + https://github.com/fmtlib/fmt/issues/2472, + https://github.com/fmtlib/fmt/issues/2474, + https://github.com/fmtlib/fmt/pull/2476, + https://github.com/fmtlib/fmt/issues/2478, + https://github.com/fmtlib/fmt/issues/2479, + https://github.com/fmtlib/fmt/issues/2481, + https://github.com/fmtlib/fmt/pull/2482, + https://github.com/fmtlib/fmt/pull/2483, + https://github.com/fmtlib/fmt/issues/2490, + https://github.com/fmtlib/fmt/pull/2491, + https://github.com/fmtlib/fmt/pull/2510, + https://github.com/fmtlib/fmt/pull/2518, + https://github.com/fmtlib/fmt/issues/2528, + https://github.com/fmtlib/fmt/pull/2529, + https://github.com/fmtlib/fmt/pull/2539, + https://github.com/fmtlib/fmt/issues/2540, + https://github.com/fmtlib/fmt/pull/2545, + https://github.com/fmtlib/fmt/pull/2555, + https://github.com/fmtlib/fmt/issues/2557, + https://github.com/fmtlib/fmt/issues/2570, + https://github.com/fmtlib/fmt/pull/2573, + https://github.com/fmtlib/fmt/pull/2582, + https://github.com/fmtlib/fmt/issues/2605, + https://github.com/fmtlib/fmt/pull/2611, + https://github.com/fmtlib/fmt/pull/2647, + https://github.com/fmtlib/fmt/issues/2627, + https://github.com/fmtlib/fmt/pull/2630, + https://github.com/fmtlib/fmt/issues/2635, + https://github.com/fmtlib/fmt/issues/2638, + https://github.com/fmtlib/fmt/issues/2653, + https://github.com/fmtlib/fmt/issues/2654, + https://github.com/fmtlib/fmt/issues/2661, + https://github.com/fmtlib/fmt/pull/2664, + https://github.com/fmtlib/fmt/pull/2684). + Thanks @DanielaE, @mwinterb, @cdacamar, @TrebledJ, @bodomartin, @cquammen, + @white238, @mmarkeloff, @palacaze, @jcelerier, @mborn-adi, @BrukerJWD, + @spyridon97, @phprus, @oliverlee, @joshessman-llnl, @akohlmey, @timkalu, + @olupton, @Acretock, @alexezeder, @andrewcorrigan, @lucpelletier and + @HazardyKnusperkeks. + +# 8.0.1 - 2021-07-02 + +- Fixed the version number in the inline namespace + (https://github.com/fmtlib/fmt/issues/2374). +- Added a missing presentation type check for `std::string` + (https://github.com/fmtlib/fmt/issues/2402). +- Fixed a linkage error when mixing code built with clang and gcc + (https://github.com/fmtlib/fmt/issues/2377). +- Fixed documentation issues + (https://github.com/fmtlib/fmt/pull/2396, + https://github.com/fmtlib/fmt/issues/2403, + https://github.com/fmtlib/fmt/issues/2406). Thanks @mkurdej. +- Removed dead code in FP formatter ( + https://github.com/fmtlib/fmt/pull/2398). Thanks @javierhonduco. +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2351, + https://github.com/fmtlib/fmt/issues/2359, + https://github.com/fmtlib/fmt/pull/2365, + https://github.com/fmtlib/fmt/issues/2368, + https://github.com/fmtlib/fmt/pull/2370, + https://github.com/fmtlib/fmt/pull/2376, + https://github.com/fmtlib/fmt/pull/2381, + https://github.com/fmtlib/fmt/pull/2382, + https://github.com/fmtlib/fmt/issues/2386, + https://github.com/fmtlib/fmt/pull/2389, + https://github.com/fmtlib/fmt/pull/2395, + https://github.com/fmtlib/fmt/pull/2397, + https://github.com/fmtlib/fmt/issues/2400, + https://github.com/fmtlib/fmt/issues/2401, + https://github.com/fmtlib/fmt/pull/2407). + Thanks @zx2c4, @AidanSun05, @mattiasljungstrom, @joemmett, @erengy, + @patlkli, @gsjaardema and @phprus. + +# 8.0.0 - 2021-06-21 + +- Enabled compile-time format string checks by default. For example + ([godbolt](https://godbolt.org/z/sMxcohGjz)): + + ```c++ + #include + + int main() { + fmt::print("{:d}", "I am not a number"); + } + ``` + + gives a compile-time error on compilers with C++20 `consteval` + support (gcc 10+, clang 11+) because `d` is not a valid format + specifier for a string. + + To pass a runtime string wrap it in `fmt::runtime`: + + ```c++ + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + ``` + +- Added compile-time formatting + (https://github.com/fmtlib/fmt/pull/2019, + https://github.com/fmtlib/fmt/pull/2044, + https://github.com/fmtlib/fmt/pull/2056, + https://github.com/fmtlib/fmt/pull/2072, + https://github.com/fmtlib/fmt/pull/2075, + https://github.com/fmtlib/fmt/issues/2078, + https://github.com/fmtlib/fmt/pull/2129, + https://github.com/fmtlib/fmt/pull/2326). For example + ([godbolt](https://godbolt.org/z/Mxx9d89jM)): + + ```c++ + #include + + consteval auto compile_time_itoa(int value) -> std::array { + auto result = std::array(); + fmt::format_to(result.data(), FMT_COMPILE("{}"), value); + return result; + } + + constexpr auto answer = compile_time_itoa(42); + ``` + + Most of the formatting functionality is available at compile time + with a notable exception of floating-point numbers and pointers. + Thanks @alexezeder. + +- Optimized handling of format specifiers during format string + compilation. For example, hexadecimal formatting (`"{:x}"`) is now + 3-7x faster than before when using `format_to` with format string + compilation and a stack-allocated buffer + (https://github.com/fmtlib/fmt/issues/1944). + + Before (7.1.3): + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileOld/0 15.5 ns 15.5 ns 43302898 + FMTCompileOld/42 16.6 ns 16.6 ns 43278267 + FMTCompileOld/273123 18.7 ns 18.6 ns 37035861 + FMTCompileOld/9223372036854775807 19.4 ns 19.4 ns 35243000 + ---------------------------------------------------------------------------- + + After (8.x): + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileNew/0 1.99 ns 1.99 ns 360523686 + FMTCompileNew/42 2.33 ns 2.33 ns 279865664 + FMTCompileNew/273123 3.72 ns 3.71 ns 190230315 + FMTCompileNew/9223372036854775807 5.28 ns 5.26 ns 130711631 + ---------------------------------------------------------------------------- + + It is even faster than `std::to_chars` from libc++ compiled with + clang on macOS: + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + ToChars/0 4.42 ns 4.41 ns 160196630 + ToChars/42 5.00 ns 4.98 ns 140735201 + ToChars/273123 7.26 ns 7.24 ns 95784130 + ToChars/9223372036854775807 8.77 ns 8.75 ns 75872534 + ---------------------------------------------------------------------------- + + In other cases, especially involving `std::string` construction, the + speed up is usually lower because handling format specifiers takes a + smaller fraction of the total time. + +- Added the `_cf` user-defined literal to represent a compiled format + string. It can be used instead of the `FMT_COMPILE` macro + (https://github.com/fmtlib/fmt/pull/2043, + https://github.com/fmtlib/fmt/pull/2242): + + ```c++ + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{}"), 42); // 🙁 not modern + auto s = fmt::format("{}"_cf, 42); // 🙂 modern as hell + ``` + + It requires compiler support for class types in non-type template + parameters (a C++20 feature) which is available in GCC 9.3+. + Thanks @alexezeder. + +- Format string compilation now requires `format` functions of + `formatter` specializations for user-defined types to be `const`: + + ```c++ + template <> struct fmt::formatter: formatter { + template + auto format(my_type obj, FormatContext& ctx) const { // Note const here. + // ... + } + }; + ``` + +- Added UDL-based named argument support to format string compilation + (https://github.com/fmtlib/fmt/pull/2243, + https://github.com/fmtlib/fmt/pull/2281). For example: + + ```c++ + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{answer}"), "answer"_a = 42); + ``` + + Here the argument named \"answer\" is resolved at compile time with + no runtime overhead. Thanks @alexezeder. + +- Added format string compilation support to `fmt::print` + (https://github.com/fmtlib/fmt/issues/2280, + https://github.com/fmtlib/fmt/pull/2304). Thanks @alexezeder. + +- Added initial support for compiling {fmt} as a C++20 module + (https://github.com/fmtlib/fmt/pull/2235, + https://github.com/fmtlib/fmt/pull/2240, + https://github.com/fmtlib/fmt/pull/2260, + https://github.com/fmtlib/fmt/pull/2282, + https://github.com/fmtlib/fmt/pull/2283, + https://github.com/fmtlib/fmt/pull/2288, + https://github.com/fmtlib/fmt/pull/2298, + https://github.com/fmtlib/fmt/pull/2306, + https://github.com/fmtlib/fmt/pull/2307, + https://github.com/fmtlib/fmt/pull/2309, + https://github.com/fmtlib/fmt/pull/2318, + https://github.com/fmtlib/fmt/pull/2324, + https://github.com/fmtlib/fmt/pull/2332, + https://github.com/fmtlib/fmt/pull/2340). Thanks @DanielaE. + +- Made symbols private by default reducing shared library size + (https://github.com/fmtlib/fmt/pull/2301). For example + there was a \~15% reported reduction on one platform. Thanks @sergiud. + +- Optimized includes making the result of preprocessing `fmt/format.h` + \~20% smaller with libstdc++/C++20 and slightly improving build + times (https://github.com/fmtlib/fmt/issues/1998). + +- Added support of ranges with non-const `begin` / `end` + (https://github.com/fmtlib/fmt/pull/1953). Thanks @kitegi. + +- Added support of `std::byte` and other formattable types to + `fmt::join` (https://github.com/fmtlib/fmt/issues/1981, + https://github.com/fmtlib/fmt/issues/2040, + https://github.com/fmtlib/fmt/pull/2050, + https://github.com/fmtlib/fmt/issues/2262). For example: + + ```c++ + #include + #include + #include + + int main() { + auto bytes = std::vector{std::byte(4), std::byte(2)}; + fmt::print("{}", fmt::join(bytes, "")); + } + ``` + + prints \"42\". + + Thanks @kamibo. + +- Implemented the default format for `std::chrono::system_clock` + (https://github.com/fmtlib/fmt/issues/2319, + https://github.com/fmtlib/fmt/pull/2345). For example: + + ```c++ + #include + + int main() { + fmt::print("{}", std::chrono::system_clock::now()); + } + ``` + + prints \"2021-06-18 15:22:00\" (the output depends on the current + date and time). Thanks @sunmy2019. + +- Made more chrono specifiers locale independent by default. Use the + `'L'` specifier to get localized formatting. For example: + + ```c++ + #include + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + auto monday = std::chrono::weekday(1); + fmt::print("{}\n", monday); // prints "Mon" + fmt::print("{:L}\n", monday); // prints "пн" + } + ``` + +- Improved locale handling in chrono formatting + (https://github.com/fmtlib/fmt/issues/2337, + https://github.com/fmtlib/fmt/pull/2349, + https://github.com/fmtlib/fmt/pull/2350). Thanks @phprus. + +- Deprecated `fmt/locale.h` moving the formatting functions that take + a locale to `fmt/format.h` (`char`) and `fmt/xchar` (other + overloads). This doesn\'t introduce a dependency on `` so + there is virtually no compile time effect. + +- Deprecated an undocumented `format_to` overload that takes + `basic_memory_buffer`. + +- Made parameter order in `vformat_to` consistent with `format_to` + (https://github.com/fmtlib/fmt/issues/2327). + +- Added support for time points with arbitrary durations + (https://github.com/fmtlib/fmt/issues/2208). For example: + + ```c++ + #include + + int main() { + using tp = std::chrono::time_point< + std::chrono::system_clock, std::chrono::seconds>; + fmt::print("{:%S}", tp(std::chrono::seconds(42))); + } + ``` + + prints \"42\". + +- Formatting floating-point numbers no longer produces trailing zeros + by default for consistency with `std::format`. For example: + + ```c++ + #include + + int main() { + fmt::print("{0:.3}", 1.1); + } + ``` + + prints \"1.1\". Use the `'#'` specifier to keep trailing zeros. + +- Dropped a limit on the number of elements in a range and replaced + `{}` with `[]` as range delimiters for consistency with Python\'s + `str.format`. + +- The `'L'` specifier for locale-specific numeric formatting can now + be combined with presentation specifiers as in `std::format`. For + example: + + ```c++ + #include + #include + + int main() { + std::locale::global(std::locale("fr_FR.UTF-8")); + fmt::print("{0:.2Lf}", 0.42); + } + ``` + + prints \"0,42\". The deprecated `'n'` specifier has been removed. + +- Made the `0` specifier ignored for infinity and NaN + (https://github.com/fmtlib/fmt/issues/2305, + https://github.com/fmtlib/fmt/pull/2310). Thanks @Liedtke. + +- Made the hexfloat formatting use the right alignment by default + (https://github.com/fmtlib/fmt/issues/2308, + https://github.com/fmtlib/fmt/pull/2317). Thanks @Liedtke. + +- Removed the deprecated numeric alignment (`'='`). Use the `'0'` + specifier instead. + +- Removed the deprecated `fmt/posix.h` header that has been replaced + with `fmt/os.h`. + +- Removed the deprecated `format_to_n_context`, `format_to_n_args` and + `make_format_to_n_args`. They have been replaced with + `format_context`, `` format_args` and ``make_format_args\`\` + respectively. + +- Moved `wchar_t`-specific functions and types to `fmt/xchar.h`. You + can define `FMT_DEPRECATED_INCLUDE_XCHAR` to automatically include + `fmt/xchar.h` from `fmt/format.h` but this will be disabled in the + next major release. + +- Fixed handling of the `'+'` specifier in localized formatting + (https://github.com/fmtlib/fmt/issues/2133). + +- Added support for the `'s'` format specifier that gives textual + representation of `bool` + (https://github.com/fmtlib/fmt/issues/2094, + https://github.com/fmtlib/fmt/pull/2109). For example: + + ```c++ + #include + + int main() { + fmt::print("{:s}", true); + } + ``` + + prints \"true\". Thanks @powercoderlol. + +- Made `fmt::ptr` work with function pointers + (https://github.com/fmtlib/fmt/pull/2131). For example: + + ```c++ + #include + + int main() { + fmt::print("My main: {}\n", fmt::ptr(main)); + } + ``` + + Thanks @mikecrowe. + +- The undocumented support for specializing `formatter` for pointer + types has been removed. + +- Fixed `fmt::formatted_size` with format string compilation + (https://github.com/fmtlib/fmt/pull/2141, + https://github.com/fmtlib/fmt/pull/2161). Thanks @alexezeder. + +- Fixed handling of empty format strings during format string + compilation (https://github.com/fmtlib/fmt/issues/2042): + + ```c++ + auto s = fmt::format(FMT_COMPILE("")); + ``` + + Thanks @alexezeder. + +- Fixed handling of enums in `fmt::to_string` + (https://github.com/fmtlib/fmt/issues/2036). + +- Improved width computation + (https://github.com/fmtlib/fmt/issues/2033, + https://github.com/fmtlib/fmt/issues/2091). For example: + + ```c++ + #include + + int main() { + fmt::print("{:-<10}{}\n", "你好", "世界"); + fmt::print("{:-<10}{}\n", "hello", "world"); + } + ``` + + prints + + ![](https://user-images.githubusercontent.com/576385/119840373-cea3ca80-beb9-11eb-91e0-54266c48e181.png) + + on a modern terminal. + +- The experimental fast output stream (`fmt::ostream`) is now + truncated by default for consistency with `fopen` + (https://github.com/fmtlib/fmt/issues/2018). For example: + + ```c++ + #include + + int main() { + fmt::ostream out1 = fmt::output_file("guide"); + out1.print("Zaphod"); + out1.close(); + fmt::ostream out2 = fmt::output_file("guide"); + out2.print("Ford"); + } + ``` + + writes \"Ford\" to the file \"guide\". To preserve the old file + content if any pass `fmt::file::WRONLY | fmt::file::CREATE` flags to + `fmt::output_file`. + +- Fixed moving of `fmt::ostream` that holds buffered data + (https://github.com/fmtlib/fmt/issues/2197, + https://github.com/fmtlib/fmt/pull/2198). Thanks @vtta. + +- Replaced the `fmt::system_error` exception with a function of the + same name that constructs `std::system_error` + (https://github.com/fmtlib/fmt/issues/2266). + +- Replaced the `fmt::windows_error` exception with a function of the + same name that constructs `std::system_error` with the category + returned by `fmt::system_category()` + (https://github.com/fmtlib/fmt/issues/2274, + https://github.com/fmtlib/fmt/pull/2275). The latter is + similar to `std::system_category` but correctly handles UTF-8. + Thanks @phprus. + +- Replaced `fmt::error_code` with `std::error_code` and made it + formattable (https://github.com/fmtlib/fmt/issues/2269, + https://github.com/fmtlib/fmt/pull/2270, + https://github.com/fmtlib/fmt/pull/2273). Thanks @phprus. + +- Added speech synthesis support + (https://github.com/fmtlib/fmt/pull/2206). + +- Made `format_to` work with a memory buffer that has a custom + allocator (https://github.com/fmtlib/fmt/pull/2300). + Thanks @voxmea. + +- Added `Allocator::max_size` support to `basic_memory_buffer`. + (https://github.com/fmtlib/fmt/pull/1960). Thanks @phprus. + +- Added wide string support to `fmt::join` + (https://github.com/fmtlib/fmt/pull/2236). Thanks @crbrz. + +- Made iterators passed to `formatter` specializations via a format + context satisfy C++20 `std::output_iterator` requirements + (https://github.com/fmtlib/fmt/issues/2156, + https://github.com/fmtlib/fmt/pull/2158, + https://github.com/fmtlib/fmt/issues/2195, + https://github.com/fmtlib/fmt/pull/2204). Thanks @randomnetcat. + +- Optimized the `printf` implementation + (https://github.com/fmtlib/fmt/pull/1982, + https://github.com/fmtlib/fmt/pull/1984, + https://github.com/fmtlib/fmt/pull/2016, + https://github.com/fmtlib/fmt/pull/2164). + Thanks @rimathia and @moiwi. + +- Improved detection of `constexpr` `char_traits` + (https://github.com/fmtlib/fmt/pull/2246, + https://github.com/fmtlib/fmt/pull/2257). Thanks @phprus. + +- Fixed writing to `stdout` when it is redirected to `NUL` on Windows + (https://github.com/fmtlib/fmt/issues/2080). + +- Fixed exception propagation from iterators + (https://github.com/fmtlib/fmt/issues/2097). + +- Improved `strftime` error handling + (https://github.com/fmtlib/fmt/issues/2238, + https://github.com/fmtlib/fmt/pull/2244). Thanks @yumeyao. + +- Stopped using deprecated GCC UDL template extension. + +- Added `fmt/args.h` to the install target + (https://github.com/fmtlib/fmt/issues/2096). + +- Error messages are now passed to assert when exceptions are disabled + (https://github.com/fmtlib/fmt/pull/2145). Thanks @NobodyXu. + +- Added the `FMT_MASTER_PROJECT` CMake option to control build and + install targets when {fmt} is included via `add_subdirectory` + (https://github.com/fmtlib/fmt/issues/2098, + https://github.com/fmtlib/fmt/pull/2100). + Thanks @randomizedthinking. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/2026, + https://github.com/fmtlib/fmt/pull/2122). + Thanks @luncliff and @ibaned. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/1947, + https://github.com/fmtlib/fmt/pull/1959, + https://github.com/fmtlib/fmt/pull/1963, + https://github.com/fmtlib/fmt/pull/1965, + https://github.com/fmtlib/fmt/issues/1966, + https://github.com/fmtlib/fmt/pull/1974, + https://github.com/fmtlib/fmt/pull/1975, + https://github.com/fmtlib/fmt/pull/1990, + https://github.com/fmtlib/fmt/issues/2000, + https://github.com/fmtlib/fmt/pull/2001, + https://github.com/fmtlib/fmt/issues/2002, + https://github.com/fmtlib/fmt/issues/2004, + https://github.com/fmtlib/fmt/pull/2006, + https://github.com/fmtlib/fmt/pull/2009, + https://github.com/fmtlib/fmt/pull/2010, + https://github.com/fmtlib/fmt/issues/2038, + https://github.com/fmtlib/fmt/issues/2039, + https://github.com/fmtlib/fmt/issues/2047, + https://github.com/fmtlib/fmt/pull/2053, + https://github.com/fmtlib/fmt/issues/2059, + https://github.com/fmtlib/fmt/pull/2065, + https://github.com/fmtlib/fmt/pull/2067, + https://github.com/fmtlib/fmt/pull/2068, + https://github.com/fmtlib/fmt/pull/2073, + https://github.com/fmtlib/fmt/issues/2103, + https://github.com/fmtlib/fmt/issues/2105, + https://github.com/fmtlib/fmt/pull/2106, + https://github.com/fmtlib/fmt/pull/2107, + https://github.com/fmtlib/fmt/issues/2116, + https://github.com/fmtlib/fmt/pull/2117, + https://github.com/fmtlib/fmt/issues/2118, + https://github.com/fmtlib/fmt/pull/2119, + https://github.com/fmtlib/fmt/issues/2127, + https://github.com/fmtlib/fmt/pull/2128, + https://github.com/fmtlib/fmt/issues/2140, + https://github.com/fmtlib/fmt/issues/2142, + https://github.com/fmtlib/fmt/pull/2143, + https://github.com/fmtlib/fmt/pull/2144, + https://github.com/fmtlib/fmt/issues/2147, + https://github.com/fmtlib/fmt/issues/2148, + https://github.com/fmtlib/fmt/issues/2149, + https://github.com/fmtlib/fmt/pull/2152, + https://github.com/fmtlib/fmt/pull/2160, + https://github.com/fmtlib/fmt/issues/2170, + https://github.com/fmtlib/fmt/issues/2175, + https://github.com/fmtlib/fmt/issues/2176, + https://github.com/fmtlib/fmt/pull/2177, + https://github.com/fmtlib/fmt/issues/2178, + https://github.com/fmtlib/fmt/pull/2179, + https://github.com/fmtlib/fmt/issues/2180, + https://github.com/fmtlib/fmt/issues/2181, + https://github.com/fmtlib/fmt/pull/2183, + https://github.com/fmtlib/fmt/issues/2184, + https://github.com/fmtlib/fmt/issues/2185, + https://github.com/fmtlib/fmt/pull/2186, + https://github.com/fmtlib/fmt/pull/2187, + https://github.com/fmtlib/fmt/pull/2190, + https://github.com/fmtlib/fmt/pull/2192, + https://github.com/fmtlib/fmt/pull/2194, + https://github.com/fmtlib/fmt/pull/2205, + https://github.com/fmtlib/fmt/issues/2210, + https://github.com/fmtlib/fmt/pull/2211, + https://github.com/fmtlib/fmt/pull/2215, + https://github.com/fmtlib/fmt/pull/2216, + https://github.com/fmtlib/fmt/pull/2218, + https://github.com/fmtlib/fmt/pull/2220, + https://github.com/fmtlib/fmt/issues/2228, + https://github.com/fmtlib/fmt/pull/2229, + https://github.com/fmtlib/fmt/pull/2230, + https://github.com/fmtlib/fmt/issues/2233, + https://github.com/fmtlib/fmt/pull/2239, + https://github.com/fmtlib/fmt/issues/2248, + https://github.com/fmtlib/fmt/issues/2252, + https://github.com/fmtlib/fmt/pull/2253, + https://github.com/fmtlib/fmt/pull/2255, + https://github.com/fmtlib/fmt/issues/2261, + https://github.com/fmtlib/fmt/issues/2278, + https://github.com/fmtlib/fmt/issues/2284, + https://github.com/fmtlib/fmt/pull/2287, + https://github.com/fmtlib/fmt/pull/2289, + https://github.com/fmtlib/fmt/pull/2290, + https://github.com/fmtlib/fmt/pull/2293, + https://github.com/fmtlib/fmt/issues/2295, + https://github.com/fmtlib/fmt/pull/2296, + https://github.com/fmtlib/fmt/pull/2297, + https://github.com/fmtlib/fmt/issues/2311, + https://github.com/fmtlib/fmt/pull/2313, + https://github.com/fmtlib/fmt/pull/2315, + https://github.com/fmtlib/fmt/issues/2320, + https://github.com/fmtlib/fmt/pull/2321, + https://github.com/fmtlib/fmt/pull/2323, + https://github.com/fmtlib/fmt/issues/2328, + https://github.com/fmtlib/fmt/pull/2329, + https://github.com/fmtlib/fmt/pull/2333, + https://github.com/fmtlib/fmt/pull/2338, + https://github.com/fmtlib/fmt/pull/2341). + Thanks @darklukee, @fagg, @killerbot242, @jgopel, @yeswalrus, @Finkman, + @HazardyKnusperkeks, @dkavolis, @concatime, @chronoxor, @summivox, @yNeo, + @Apache-HB, @alexezeder, @toojays, @Brainy0207, @vadz, @imsherlock, @phprus, + @white238, @yafshar, @BillyDonahue, @jstaahl, @denchat, @DanielaE, + @ilyakurdyukov, @ilmai, @JessyDL, @sergiud, @mwinterb, @sven-herrmann, + @jmelas, @twoixter, @crbrz and @upsj. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1986, + https://github.com/fmtlib/fmt/pull/2051, + https://github.com/fmtlib/fmt/issues/2057, + https://github.com/fmtlib/fmt/pull/2081, + https://github.com/fmtlib/fmt/issues/2084, + https://github.com/fmtlib/fmt/pull/2312). + Thanks @imba-tjd, @0x416c69 and @mordante. + +- Continuous integration and test improvements + (https://github.com/fmtlib/fmt/issues/1969, + https://github.com/fmtlib/fmt/pull/1991, + https://github.com/fmtlib/fmt/pull/2020, + https://github.com/fmtlib/fmt/pull/2110, + https://github.com/fmtlib/fmt/pull/2114, + https://github.com/fmtlib/fmt/issues/2196, + https://github.com/fmtlib/fmt/pull/2217, + https://github.com/fmtlib/fmt/pull/2247, + https://github.com/fmtlib/fmt/pull/2256, + https://github.com/fmtlib/fmt/pull/2336, + https://github.com/fmtlib/fmt/pull/2346). + Thanks @jgopel, @alexezeder and @DanielaE. + +The change log for versions 0.8.0 - 7.1.3 is available [here]( +doc/ChangeLog-old.md). diff --git a/libs/openal-soft/fmt-11.1.1/LICENSE b/libs/openal-soft/fmt-11.1.1/LICENSE new file mode 100644 index 00000000..1cd1ef92 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + +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. diff --git a/libs/openal-soft/fmt-11.1.1/README.md b/libs/openal-soft/fmt-11.1.1/README.md new file mode 100644 index 00000000..fd845db2 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/README.md @@ -0,0 +1,485 @@ +{fmt} + +[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux) +[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) +[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) +[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1) +[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) +[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) + +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. + +If you like this project, please consider donating to one of the funds +that help victims of the war in Ukraine: . + +[Documentation](https://fmt.dev) + +[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html) + +Q&A: ask questions on [StackOverflow with the tag +fmt](https://stackoverflow.com/questions/tagged/fmt). + +Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v). + +# Features + +- Simple [format API](https://fmt.dev/latest/api/) with positional + arguments for localization +- Implementation of [C++20 + std::format](https://en.cppreference.com/w/cpp/utility/format) and + [C++23 std::print](https://en.cppreference.com/w/cpp/io/print) +- [Format string syntax](https://fmt.dev/latest/syntax/) similar + to Python\'s + [format](https://docs.python.org/3/library/stdtypes.html#str.format) +- Fast IEEE 754 floating-point formatter with correct rounding, + shortness and round-trip guarantees using the + [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm +- Portable Unicode support +- Safe [printf + implementation](https://fmt.dev/latest/api/#printf-formatting) + including the POSIX extension for positional arguments +- Extensibility: [support for user-defined + types](https://fmt.dev/latest/api/#formatting-user-defined-types) +- High performance: faster than common standard library + implementations of `(s)printf`, iostreams, `to_string` and + `to_chars`, see [Speed tests](#speed-tests) and [Converting a + hundred million integers to strings per + second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) +- Small code size both in terms of source code with the minimum + configuration consisting of just three files, `core.h`, `format.h` + and `format-inl.h`, and compiled code; see [Compile time and code + bloat](#compile-time-and-code-bloat) +- Reliability: the library has an extensive set of + [tests](https://github.com/fmtlib/fmt/tree/master/test) and is + [continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1) +- Safety: the library is fully type-safe, errors in format strings can + be reported at compile time, automatic memory management prevents + buffer overflow errors +- Ease of use: small self-contained code base, no external + dependencies, permissive MIT + [license](https://github.com/fmtlib/fmt/blob/master/LICENSE) +- [Portability](https://fmt.dev/latest/#portability) with + consistent output across platforms and support for older compilers +- Clean warning-free codebase even on high warning levels such as + `-Wall -Wextra -pedantic` +- Locale independence by default +- Optional header-only configuration enabled with the + `FMT_HEADER_ONLY` macro + +See the [documentation](https://fmt.dev) for more details. + +# Examples + +**Print to stdout** ([run](https://godbolt.org/z/Tevcjh)) + +``` c++ +#include + +int main() { + fmt::print("Hello, world!\n"); +} +``` + +**Format a string** ([run](https://godbolt.org/z/oK8h33)) + +``` c++ +std::string s = fmt::format("The answer is {}.", 42); +// s == "The answer is 42." +``` + +**Format a string using positional arguments** +([run](https://godbolt.org/z/Yn7Txe)) + +``` c++ +std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); +// s == "I'd rather be happy than right." +``` + +**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W)) + +``` c++ +#include + +int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("Date and time: {}\n", now); + fmt::print("Time: {:%H:%M}\n", now); +} +``` + +Output: + + Date and time: 2023-12-26 19:10:31.557195597 + Time: 19:10 + +**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7)) + +``` c++ +#include +#include + +int main() { + std::vector v = {1, 2, 3}; + fmt::print("{}\n", v); +} +``` + +Output: + + [1, 2, 3] + +**Check a format string at compile time** + +``` c++ +std::string s = fmt::format("{:d}", "I am not a number"); +``` + +This gives a compile-time error in C++20 because `d` is an invalid +format specifier for a string. + +**Write a file from a single thread** + +``` c++ +#include + +int main() { + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); +} +``` + +This can be [5 to 9 times faster than +fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). + +**Print with colors and text styles** + +``` c++ +#include + +int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "你好{}!\n", "世界"); +} +``` + +Output on a modern terminal with Unicode support: + +![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) + +# Benchmarks + +## Speed tests + +| Library | Method | Run Time, s | +|-------------------|---------------|-------------| +| libc | printf | 0.91 | +| libc++ | std::ostream | 2.49 | +| {fmt} 9.1 | fmt::print | 0.74 | +| Boost Format 1.80 | boost::format | 6.26 | +| Folly Format | folly::format | 1.87 | + +{fmt} is the fastest of the benchmarked methods, \~20% faster than +`printf`. + +The above results were generated by building `tinyformat_test.cpp` on +macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and +taking the best of three runs. In the test, the format string +`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 +times with output sent to `/dev/null`; for further details refer to the +[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc). + +{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on +IEEE754 `float` and `double` formatting +([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster +than [double-conversion](https://github.com/google/double-conversion) +and [ryu](https://github.com/ulfjack/ryu): + +[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html) + +## Compile time and code bloat + +The script [bloat-test.py][test] from [format-benchmark][bench] tests compile +time and code bloat for nontrivial projects. It generates 100 translation units +and uses `printf()` or its alternative five times in each to simulate a +medium-sized project. The resulting executable size and compile time (Apple +clang version 15.0.0 (clang-1500.1.0.2.5), macOS Sonoma, best of three) is shown +in the following tables. + +[test]: https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py +[bench]: https://github.com/fmtlib/format-benchmark + +**Optimized build (-O3)** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 1.6 | 54 | 50 | +| IOStreams | 25.9 | 98 | 84 | +| fmt 83652df | 4.8 | 54 | 50 | +| tinyformat | 29.1 | 161 | 136 | +| Boost Format | 55.0 | 530 | 317 | + +{fmt} is fast to compile and is comparable to `printf` in terms of per-call +binary size (within a rounding error on this system). + +**Non-optimized build** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 1.4 | 54 | 50 | +| IOStreams | 23.4 | 92 | 68 | +| {fmt} 83652df | 4.4 | 89 | 85 | +| tinyformat | 24.5 | 204 | 161 | +| Boost Format | 36.4 | 831 | 462 | + +`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries +to compare formatting function overhead only. Boost Format is a +header-only library so it doesn\'t provide any linkage options. + +## Running the tests + +Please refer to [Building the +library](https://fmt.dev/latest/get-started/#building-from-source) for +instructions on how to build the library and run the unit tests. + +Benchmarks reside in a separate repository, +[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to +run the benchmarks you first need to clone this repository and generate +Makefiles with CMake: + + $ git clone --recursive https://github.com/fmtlib/format-benchmark.git + $ cd format-benchmark + $ cmake . + +Then you can run the speed test: + + $ make speed-test + +or the bloat test: + + $ make bloat-test + +# Migrating code + +[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v18 provides the +[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html) +check that is capable of converting occurrences of `printf` and +`fprintf` to `fmt::print` if configured to do so. (By default it +converts to `std::print`.) + +# Notable projects using this library + +- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform + real-time strategy game +- [AMPL/MP](https://github.com/ampl/mp): an open-source library for + mathematical programming +- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source, + distributed, transactional key-value store +- [Aseprite](https://github.com/aseprite/aseprite): animated sprite + editor & pixel art tool +- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft + operations suite +- [Blizzard Battle.net](https://battle.net/): an online gaming + platform +- [Celestia](https://celestia.space/): real-time 3D visualization of + space +- [Ceph](https://ceph.com/): a scalable distributed storage system +- [ccache](https://ccache.dev/): a compiler cache +- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an + analytical database management system +- [ContextVision](https://www.contextvision.com/): medical imaging software +- [Contour](https://github.com/contour-terminal/contour/): a modern + terminal emulator +- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous + underwater vehicle +- [Drake](https://drake.mit.edu/): a planning, control, and analysis + toolbox for nonlinear dynamical systems (MIT) +- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and + communication bus (Lyft) +- [FiveM](https://fivem.net/): a modification framework for GTA V +- [fmtlog](https://github.com/MengRao/fmtlog): a performant + fmtlib-style logging library with latency in nanoseconds +- [Folly](https://github.com/facebook/folly): Facebook open-source + library +- [GemRB](https://gemrb.org/): a portable open-source implementation + of Bioware's Infinity Engine +- [Grand Mountain + Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/): + a beautiful open-world ski & snowboarding game +- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs + Player Gaming Network with tweaks +- [KBEngine](https://github.com/kbengine/kbengine): an open-source + MMOG server engine +- [Keypirinha](https://keypirinha.com/): a semantic launcher for + Windows +- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software +- [Knuth](https://kth.cash/): high-performance Bitcoin full-node +- [libunicode](https://github.com/contour-terminal/libunicode/): a + modern C++17 Unicode library +- [MariaDB](https://mariadb.org/): relational database management + system +- [Microsoft Verona](https://github.com/microsoft/verona): research + programming language for concurrent ownership +- [MongoDB](https://mongodb.com/): distributed document database +- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small + tool to generate randomized datasets +- [OpenSpace](https://openspaceproject.com/): an open-source + astrovisualization framework +- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server, + compatible with most Ultima Online clients +- [PyTorch](https://github.com/pytorch/pytorch): an open-source + machine learning library +- [quasardb](https://www.quasardb.net/): a distributed, + high-performance, associative database +- [Quill](https://github.com/odygrd/quill): asynchronous low-latency + logging library +- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to + simplify navigation, and execute complex multi-line terminal + command sequences +- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis + cluster proxy +- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka® + replacement for mission-critical systems written in C++ +- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and + client library +- [Salesforce Analytics + Cloud](https://www.salesforce.com/analytics-cloud/overview/): + business intelligence software +- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL + data store that can handle 1 million transactions per second on a + single server +- [Seastar](http://www.seastar-project.org/): an advanced, open-source + C++ framework for high-performance server applications on modern + hardware +- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging + library +- [Stellar](https://www.stellar.org/): financial platform +- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator +- [TrinityCore](https://github.com/TrinityCore/TrinityCore): + open-source MMORPG framework +- [🐙 userver framework](https://userver.tech/): open-source + asynchronous framework with a rich set of abstractions and database + drivers +- [Windows Terminal](https://github.com/microsoft/terminal): the new + Windows terminal + +[More\...](https://github.com/search?q=fmtlib&type=Code) + +If you are aware of other projects using this library, please let me +know by [email](mailto:victor.zverovich@gmail.com) or by submitting an +[issue](https://github.com/fmtlib/fmt/issues). + +# Motivation + +So why yet another formatting library? + +There are plenty of methods for doing this task, from standard ones like +the printf family of function and iostreams to Boost Format and +FastFormat libraries. The reason for creating a new library is that +every existing solution that I found either had serious issues or +didn\'t provide all the features I needed. + +## printf + +The good thing about `printf` is that it is pretty fast and readily +available being a part of the C standard library. The main drawback is +that it doesn\'t support user-defined types. `printf` also has safety +issues although they are somewhat mitigated with [\_\_attribute\_\_ +((format (printf, +\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in +GCC. There is a POSIX extension that adds positional arguments required +for +[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization) +to `printf` but it is not a part of C99 and may not be available on some +platforms. + +## iostreams + +The main issue with iostreams is best illustrated with an example: + +``` c++ +std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; +``` + +which is a lot of typing compared to printf: + +``` c++ +printf("%.2f\n", 1.23456); +``` + +Matthew Wilson, the author of FastFormat, called this \"chevron hell\". +iostreams don\'t support positional arguments by design. + +The good part is that iostreams support user-defined types and are safe +although error handling is awkward. + +## Boost Format + +This is a very powerful library that supports both `printf`-like format +strings and positional arguments. Its main drawback is performance. +According to various benchmarks, it is much slower than other methods +considered here. Boost Format also has excessive build times and severe +code bloat issues (see [Benchmarks](#benchmarks)). + +## FastFormat + +This is an interesting library that is fast, safe and has positional +arguments. However, it has significant limitations, citing its author: + +> Three features that have no hope of being accommodated within the +> current design are: +> +> - Leading zeros (or any other non-space padding) +> - Octal/hexadecimal encoding +> - Runtime width/alignment specification + +It is also quite big and has a heavy dependency, on STLSoft, which might be +too restrictive for use in some projects. + +## Boost Spirit.Karma + +This is not a formatting library but I decided to include it here for +completeness. As iostreams, it suffers from the problem of mixing +verbatim text with arguments. The library is pretty fast, but slower on +integer formatting than `fmt::format_to` with format string compilation +on Karma\'s own benchmark, see [Converting a hundred million integers to +strings per +second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). + +# License + +{fmt} is distributed under the MIT +[license](https://github.com/fmtlib/fmt/blob/master/LICENSE). + +# Documentation License + +The [Format String Syntax](https://fmt.dev/latest/syntax/) section +in the documentation is based on the one from Python [string module +documentation](https://docs.python.org/3/library/string.html#module-string). +For this reason, the documentation is distributed under the Python +Software Foundation license available in +[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt). +It only applies if you distribute the documentation of {fmt}. + +# Maintainers + +The {fmt} library is maintained by Victor Zverovich +([vitaut](https://github.com/vitaut)) with contributions from many other +people. See +[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and +[Releases](https://github.com/fmtlib/fmt/releases) for some of the +names. Let us know if your contribution is not listed or mentioned +incorrectly and we\'ll make it right. + +# Security Policy + +To report a security issue, please disclose it at [security +advisory](https://github.com/fmtlib/fmt/security/advisories/new). + +This project is maintained by a team of volunteers on a +reasonable-effort basis. As such, please give us at least *90* days to +work on a fix before public exposure. diff --git a/libs/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md b/libs/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md new file mode 100644 index 00000000..3f31d1e9 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/ChangeLog-old.md @@ -0,0 +1,3290 @@ +# 7.1.3 - 2020-11-24 + +- Fixed handling of buffer boundaries in `format_to_n` + (https://github.com/fmtlib/fmt/issues/1996, + https://github.com/fmtlib/fmt/issues/2029). +- Fixed linkage errors when linking with a shared library + (https://github.com/fmtlib/fmt/issues/2011). +- Reintroduced ostream support to range formatters + (https://github.com/fmtlib/fmt/issues/2014). +- Worked around an issue with mixing std versions in gcc + (https://github.com/fmtlib/fmt/issues/2017). + +# 7.1.2 - 2020-11-04 + +- Fixed floating point formatting with large precision + (https://github.com/fmtlib/fmt/issues/1976). + +# 7.1.1 - 2020-11-01 + +- Fixed ABI compatibility with 7.0.x + (https://github.com/fmtlib/fmt/issues/1961). +- Added the `FMT_ARM_ABI_COMPATIBILITY` macro to work around ABI + incompatibility between GCC and Clang on ARM + (https://github.com/fmtlib/fmt/issues/1919). +- Worked around a SFINAE bug in GCC 8 + (https://github.com/fmtlib/fmt/issues/1957). +- Fixed linkage errors when building with GCC\'s LTO + (https://github.com/fmtlib/fmt/issues/1955). +- Fixed a compilation error when building without `__builtin_clz` or + equivalent (https://github.com/fmtlib/fmt/pull/1968). + Thanks @tohammer. +- Fixed a sign conversion warning + (https://github.com/fmtlib/fmt/pull/1964). Thanks @OptoCloud. + +# 7.1.0 - 2020-10-25 + +- Switched from + [Grisu3](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) + to [Dragonbox](https://github.com/jk-jeon/dragonbox) for the default + floating-point formatting which gives the shortest decimal + representation with round-trip guarantee and correct rounding + (https://github.com/fmtlib/fmt/pull/1882, + https://github.com/fmtlib/fmt/pull/1887, + https://github.com/fmtlib/fmt/pull/1894). This makes {fmt} + up to 20-30x faster than common implementations of + `std::ostringstream` and `sprintf` on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark) and + faster than double-conversion and Ryū: + + ![](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png) + + It is possible to get even better performance at the cost of larger + binary size by compiling with the `FMT_USE_FULL_CACHE_DRAGONBOX` + macro set to 1. + + Thanks @jk-jeon. + +- Added an experimental unsynchronized file output API which, together + with [format string + compilation](https://fmt.dev/latest/api.html#compile-api), can give + [5-9 times speed up compared to + fprintf](https://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html) + on common platforms ([godbolt](https://godbolt.org/z/nsTcG8)): + + ```c++ + #include + + int main() { + auto f = fmt::output_file("guide"); + f.print("The answer is {}.", 42); + } + ``` + +- Added a formatter for `std::chrono::time_point` + (https://github.com/fmtlib/fmt/issues/1819, + https://github.com/fmtlib/fmt/pull/1837). For example + ([godbolt](https://godbolt.org/z/c4M6fh)): + + ```c++ + #include + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("The time is {:%H:%M:%S}.\n", now); + } + ``` + + Thanks @adamburgess. + +- Added support for ranges with non-const `begin`/`end` to `fmt::join` + (https://github.com/fmtlib/fmt/issues/1784, + https://github.com/fmtlib/fmt/pull/1786). For example + ([godbolt](https://godbolt.org/z/jP63Tv)): + + ```c++ + #include + #include + + int main() { + using std::literals::string_literals::operator""s; + auto strs = std::array{"a"s, "bb"s, "ccc"s}; + auto range = strs | ranges::views::filter( + [] (const std::string &x) { return x.size() != 2; } + ); + fmt::print("{}\n", fmt::join(range, "")); + } + ``` + + prints \"accc\". + + Thanks @tonyelewis. + +- Added a `memory_buffer::append` overload that takes a range + (https://github.com/fmtlib/fmt/pull/1806). Thanks @BRevzin. + +- Improved handling of single code units in `FMT_COMPILE`. For + example: + + ```c++ + #include + + char* f(char* buf) { + return fmt::format_to(buf, FMT_COMPILE("x{}"), 42); + } + ``` + + compiles to just ([godbolt](https://godbolt.org/z/5vncz3)): + + ```asm + _Z1fPc: + movb $120, (%rdi) + xorl %edx, %edx + cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip) + movl $3, %eax + seta %dl + subl %edx, %eax + movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx + cltq + addq %rdi, %rax + movw %dx, -2(%rax) + ret + ``` + + Here a single `mov` instruction writes `'x'` (`$120`) to the output + buffer. + +- Added dynamic width support to format string compilation + (https://github.com/fmtlib/fmt/issues/1809). + +- Improved error reporting for unformattable types: now you\'ll get + the type name directly in the error message instead of the note: + + ```c++ + #include + + struct how_about_no {}; + + int main() { + fmt::print("{}", how_about_no()); + } + ``` + + Error ([godbolt](https://godbolt.org/z/GoxM4e)): + + `fmt/core.h:1438:3: error: static_assert failed due to requirement 'fmt::v7::formattable()' "Cannot format an argument. To make type T formattable provide a formatter specialization: https://fmt.dev/latest/api.html#udt" ...` + +- Added the + [make_args_checked](https://fmt.dev/7.1.0/api.html#argument-lists) + function template that allows you to write formatting functions with + compile-time format string checks and avoid binary code bloat + ([godbolt](https://godbolt.org/z/PEf9qr)): + + ```c++ + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + ``` + +- Replaced `snprintf` fallback with a faster internal IEEE 754 `float` + and `double` formatter for arbitrary precision. For example + ([godbolt](https://godbolt.org/z/dPhWvj)): + + ```c++ + #include + + int main() { + fmt::print("{:.500}\n", 4.9406564584124654E-324); + } + ``` + + prints + + `4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324`. + +- Made `format_to_n` and `formatted_size` part of the [core + API](https://fmt.dev/latest/api.html#core-api) + ([godbolt](https://godbolt.org/z/sPjY1K)): + + ```c++ + #include + + int main() { + char buffer[10]; + auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42); + } + ``` + +- Added `fmt::format_to_n` overload with format string compilation + (https://github.com/fmtlib/fmt/issues/1764, + https://github.com/fmtlib/fmt/pull/1767, + https://github.com/fmtlib/fmt/pull/1869). For example + ([godbolt](https://godbolt.org/z/93h86q)): + + ```c++ + #include + + int main() { + char buffer[8]; + fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42); + } + ``` + + Thanks @Kurkin and @alexezeder. + +- Added `fmt::format_to` overload that take `text_style` + (https://github.com/fmtlib/fmt/issues/1593, + https://github.com/fmtlib/fmt/issues/1842, + https://github.com/fmtlib/fmt/pull/1843). For example + ([godbolt](https://godbolt.org/z/91153r)): + + ```c++ + #include + + int main() { + std::string out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + } + ``` + + Thanks @Naios. + +- Made the `'#'` specifier emit trailing zeros in addition to the + decimal point (https://github.com/fmtlib/fmt/issues/1797). + For example ([godbolt](https://godbolt.org/z/bhdcW9)): + + ```c++ + #include + + int main() { + fmt::print("{:#.2g}", 0.5); + } + ``` + + prints `0.50`. + +- Changed the default floating point format to not include `.0` for + consistency with `std::format` and `std::to_chars` + (https://github.com/fmtlib/fmt/issues/1893, + https://github.com/fmtlib/fmt/issues/1943). It is possible + to get the decimal point and trailing zero with the `#` specifier. + +- Fixed an issue with floating-point formatting that could result in + addition of a non-significant trailing zero in rare cases e.g. + `1.00e-34` instead of `1.0e-34` + (https://github.com/fmtlib/fmt/issues/1873, + https://github.com/fmtlib/fmt/issues/1917). + +- Made `fmt::to_string` fallback on `ostream` insertion operator if + the `formatter` specialization is not provided + (https://github.com/fmtlib/fmt/issues/1815, + https://github.com/fmtlib/fmt/pull/1829). Thanks @alexezeder. + +- Added support for the append mode to the experimental file API and + improved `fcntl.h` detection. + (https://github.com/fmtlib/fmt/pull/1847, + https://github.com/fmtlib/fmt/pull/1848). Thanks @t-wiser. + +- Fixed handling of types that have both an implicit conversion + operator and an overloaded `ostream` insertion operator + (https://github.com/fmtlib/fmt/issues/1766). + +- Fixed a slicing issue in an internal iterator type + (https://github.com/fmtlib/fmt/pull/1822). Thanks @BRevzin. + +- Fixed an issue in locale-specific integer formatting + (https://github.com/fmtlib/fmt/issues/1927). + +- Fixed handling of exotic code unit types + (https://github.com/fmtlib/fmt/issues/1870, + https://github.com/fmtlib/fmt/issues/1932). + +- Improved `FMT_ALWAYS_INLINE` + (https://github.com/fmtlib/fmt/pull/1878). Thanks @jk-jeon. + +- Removed dependency on `windows.h` + (https://github.com/fmtlib/fmt/pull/1900). Thanks @bernd5. + +- Optimized counting of decimal digits on MSVC + (https://github.com/fmtlib/fmt/pull/1890). Thanks @mwinterb. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1772, + https://github.com/fmtlib/fmt/pull/1775, + https://github.com/fmtlib/fmt/pull/1792, + https://github.com/fmtlib/fmt/pull/1838, + https://github.com/fmtlib/fmt/pull/1888, + https://github.com/fmtlib/fmt/pull/1918, + https://github.com/fmtlib/fmt/pull/1939). + Thanks @leolchat, @pepsiman, @Klaim, @ravijanjam, @francesco-st and @udnaan. + +- Added the `FMT_REDUCE_INT_INSTANTIATIONS` CMake option that reduces + the binary code size at the cost of some integer formatting + performance. This can be useful for extremely memory-constrained + embedded systems + (https://github.com/fmtlib/fmt/issues/1778, + https://github.com/fmtlib/fmt/pull/1781). Thanks @kammce. + +- Added the `FMT_USE_INLINE_NAMESPACES` macro to control usage of + inline namespaces + (https://github.com/fmtlib/fmt/pull/1945). Thanks @darklukee. + +- Improved build configuration + (https://github.com/fmtlib/fmt/pull/1760, + https://github.com/fmtlib/fmt/pull/1770, + https://github.com/fmtlib/fmt/issues/1779, + https://github.com/fmtlib/fmt/pull/1783, + https://github.com/fmtlib/fmt/pull/1823). + Thanks @dvetutnev, @xvitaly, @tambry, @medithe and @martinwuehrer. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1790, + https://github.com/fmtlib/fmt/pull/1802, + https://github.com/fmtlib/fmt/pull/1808, + https://github.com/fmtlib/fmt/issues/1810, + https://github.com/fmtlib/fmt/issues/1811, + https://github.com/fmtlib/fmt/pull/1812, + https://github.com/fmtlib/fmt/pull/1814, + https://github.com/fmtlib/fmt/pull/1816, + https://github.com/fmtlib/fmt/pull/1817, + https://github.com/fmtlib/fmt/pull/1818, + https://github.com/fmtlib/fmt/issues/1825, + https://github.com/fmtlib/fmt/pull/1836, + https://github.com/fmtlib/fmt/pull/1855, + https://github.com/fmtlib/fmt/pull/1856, + https://github.com/fmtlib/fmt/pull/1860, + https://github.com/fmtlib/fmt/pull/1877, + https://github.com/fmtlib/fmt/pull/1879, + https://github.com/fmtlib/fmt/pull/1880, + https://github.com/fmtlib/fmt/issues/1896, + https://github.com/fmtlib/fmt/pull/1897, + https://github.com/fmtlib/fmt/pull/1898, + https://github.com/fmtlib/fmt/issues/1904, + https://github.com/fmtlib/fmt/pull/1908, + https://github.com/fmtlib/fmt/issues/1911, + https://github.com/fmtlib/fmt/issues/1912, + https://github.com/fmtlib/fmt/issues/1928, + https://github.com/fmtlib/fmt/pull/1929, + https://github.com/fmtlib/fmt/issues/1935, + https://github.com/fmtlib/fmt/pull/1937, + https://github.com/fmtlib/fmt/pull/1942, + https://github.com/fmtlib/fmt/issues/1949). + Thanks @TheQwertiest, @medithe, @martinwuehrer, @n16h7hunt3r, @Othereum, + @gsjaardema, @AlexanderLanin, @gcerretani, @chronoxor, @noizefloor, + @akohlmey, @jk-jeon, @rimathia, @rglarix, @moiwi, @heckad, @MarcDirven. + @BartSiwek and @darklukee. + +# 7.0.3 - 2020-08-06 + +- Worked around broken `numeric_limits` for 128-bit integers + (https://github.com/fmtlib/fmt/issues/1787). +- Added error reporting on missing named arguments + (https://github.com/fmtlib/fmt/issues/1796). +- Stopped using 128-bit integers with clang-cl + (https://github.com/fmtlib/fmt/pull/1800). Thanks @Kingcom. +- Fixed issues in locale-specific integer formatting + (https://github.com/fmtlib/fmt/issues/1782, + https://github.com/fmtlib/fmt/issues/1801). + +# 7.0.2 - 2020-07-29 + +- Worked around broken `numeric_limits` for 128-bit integers + (https://github.com/fmtlib/fmt/issues/1725). +- Fixed compatibility with CMake 3.4 + (https://github.com/fmtlib/fmt/issues/1779). +- Fixed handling of digit separators in locale-specific formatting + (https://github.com/fmtlib/fmt/issues/1782). + +# 7.0.1 - 2020-07-07 + +- Updated the inline version namespace name. +- Worked around a gcc bug in mangling of alias templates + (https://github.com/fmtlib/fmt/issues/1753). +- Fixed a linkage error on Windows + (https://github.com/fmtlib/fmt/issues/1757). Thanks @Kurkin. +- Fixed minor issues with the documentation. + +# 7.0.0 - 2020-07-05 + +- Reduced the library size. For example, on macOS a stripped test + binary statically linked with {fmt} [shrank from \~368k to less than + 100k](http://www.zverovich.net/2020/05/21/reducing-library-size.html). + +- Added a simpler and more efficient [format string compilation + API](https://fmt.dev/7.0.0/api.html#compile-api): + + ```c++ + #include + + // Converts 42 into std::string using the most efficient method and no + // runtime format string processing. + std::string s = fmt::format(FMT_COMPILE("{}"), 42); + ``` + + The old `fmt::compile` API is now deprecated. + +- Optimized integer formatting: `format_to` with format string + compilation and a stack-allocated buffer is now [faster than + to_chars on both libc++ and + libstdc++](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). + +- Optimized handling of small format strings. For example, + + ```c++ + fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5) + ``` + + is now \~40% faster + (https://github.com/fmtlib/fmt/issues/1685). + +- Applied extern templates to improve compile times when using the + core API and `fmt/format.h` + (https://github.com/fmtlib/fmt/issues/1452). For example, + on macOS with clang the compile time of a test translation unit + dropped from 2.3s to 0.3s with `-O2` and from 0.6s to 0.3s with the + default settings (`-O0`). + + Before (`-O2`): + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total + + After (`-O2`): + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total + + Before (default): + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total + + After (default): + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total + + It is still recommended to use `fmt/core.h` instead of + `fmt/format.h` but the compile time difference is now smaller. + Thanks @alex3d for the suggestion. + +- Named arguments are now stored on stack (no dynamic memory + allocations) and the compiled code is more compact and efficient. + For example + + ```c++ + #include + + int main() { + fmt::print("The answer is {answer}\n", fmt::arg("answer", 42)); + } + ``` + + compiles to just ([godbolt](https://godbolt.org/z/NcfEp_)) + + ```asm + .LC0: + .string "answer" + .LC1: + .string "The answer is {answer}\n" + main: + sub rsp, 56 + mov edi, OFFSET FLAT:.LC1 + mov esi, 23 + movabs rdx, 4611686018427387905 + lea rax, [rsp+32] + lea rcx, [rsp+16] + mov QWORD PTR [rsp+8], 1 + mov QWORD PTR [rsp], rax + mov DWORD PTR [rsp+16], 42 + mov QWORD PTR [rsp+32], OFFSET FLAT:.LC0 + mov DWORD PTR [rsp+40], 0 + call fmt::v6::vprint(fmt::v6::basic_string_view, + fmt::v6::format_args) + xor eax, eax + add rsp, 56 + ret + + .L.str.1: + .asciz "answer" + ``` + +- Implemented compile-time checks for dynamic width and precision + (https://github.com/fmtlib/fmt/issues/1614): + + ```c++ + #include + + int main() { + fmt::print(FMT_STRING("{0:{1}}"), 42); + } + ``` + + now gives a compilation error because argument 1 doesn\'t exist: + + In file included from test.cc:1: + include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be + initialized by a constant expression + FMT_CONSTEXPR_DECL bool invalid_format = + ^ + ... + include/fmt/core.h:569:26: note: in call to + '&checker(s, {}).context_->on_error(&"argument not found"[0])' + if (id >= num_args_) on_error("argument not found"); + ^ + +- Added sentinel support to `fmt::join` + (https://github.com/fmtlib/fmt/pull/1689) + + ```c++ + struct zstring_sentinel {}; + bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } + bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } + + struct zstring { + const char* p; + const char* begin() const { return p; } + zstring_sentinel end() const { return {}; } + }; + + auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_")); + // s == "h_e_l_l_o" + ``` + + Thanks @BRevzin. + +- Added support for named arguments, `clear` and `reserve` to + `dynamic_format_arg_store` + (https://github.com/fmtlib/fmt/issues/1655, + https://github.com/fmtlib/fmt/pull/1663, + https://github.com/fmtlib/fmt/pull/1674, + https://github.com/fmtlib/fmt/pull/1677). Thanks @vsolontsov-ll. + +- Added support for the `'c'` format specifier to integral types for + compatibility with `std::format` + (https://github.com/fmtlib/fmt/issues/1652). + +- Replaced the `'n'` format specifier with `'L'` for compatibility + with `std::format` + (https://github.com/fmtlib/fmt/issues/1624). The `'n'` + specifier can be enabled via the `FMT_DEPRECATED_N_SPECIFIER` macro. + +- The `'='` format specifier is now disabled by default for + compatibility with `std::format`. It can be enabled via the + `FMT_DEPRECATED_NUMERIC_ALIGN` macro. + +- Removed the following deprecated APIs: + + - `FMT_STRING_ALIAS` and `fmt` macros - replaced by `FMT_STRING` + - `fmt::basic_string_view::char_type` - replaced by + `fmt::basic_string_view::value_type` + - `convert_to_int` + - `format_arg_store::types` + - `*parse_context` - replaced by `*format_parse_context` + - `FMT_DEPRECATED_INCLUDE_OS` + - `FMT_DEPRECATED_PERCENT` - incompatible with `std::format` + - `*writer` - replaced by compiled format API + +- Renamed the `internal` namespace to `detail` + (https://github.com/fmtlib/fmt/issues/1538). The former is + still provided as an alias if the `FMT_USE_INTERNAL` macro is + defined. + +- Improved compatibility between `fmt::printf` with the standard specs + (https://github.com/fmtlib/fmt/issues/1595, + https://github.com/fmtlib/fmt/pull/1682, + https://github.com/fmtlib/fmt/pull/1683, + https://github.com/fmtlib/fmt/pull/1687, + https://github.com/fmtlib/fmt/pull/1699). Thanks @rimathia. + +- Fixed handling of `operator<<` overloads that use `copyfmt` + (https://github.com/fmtlib/fmt/issues/1666). + +- Added the `FMT_OS` CMake option to control inclusion of OS-specific + APIs in the fmt target. This can be useful for embedded platforms + (https://github.com/fmtlib/fmt/issues/1654, + https://github.com/fmtlib/fmt/pull/1656). Thanks @kwesolowski. + +- Replaced `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` with the + `FMT_FUZZ` macro to prevent interfering with fuzzing of projects + using {fmt} (https://github.com/fmtlib/fmt/pull/1650). + Thanks @asraa. + +- Fixed compatibility with emscripten + (https://github.com/fmtlib/fmt/issues/1636, + https://github.com/fmtlib/fmt/pull/1637). Thanks @ArthurSonzogni. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/704, + https://github.com/fmtlib/fmt/pull/1643, + https://github.com/fmtlib/fmt/pull/1660, + https://github.com/fmtlib/fmt/pull/1681, + https://github.com/fmtlib/fmt/pull/1691, + https://github.com/fmtlib/fmt/pull/1706, + https://github.com/fmtlib/fmt/pull/1714, + https://github.com/fmtlib/fmt/pull/1721, + https://github.com/fmtlib/fmt/pull/1739, + https://github.com/fmtlib/fmt/pull/1740, + https://github.com/fmtlib/fmt/pull/1741, + https://github.com/fmtlib/fmt/pull/1751). + Thanks @senior7515, @lsr0, @puetzk, @fpelliccioni, Alexey Kuzmenko, @jelly, + @claremacrae, @jiapengwen, @gsjaardema and @alexey-milovidov. + +- Implemented various build configuration fixes and improvements + (https://github.com/fmtlib/fmt/pull/1603, + https://github.com/fmtlib/fmt/pull/1657, + https://github.com/fmtlib/fmt/pull/1702, + https://github.com/fmtlib/fmt/pull/1728). + Thanks @scramsby, @jtojnar, @orivej and @flagarde. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1616, + https://github.com/fmtlib/fmt/issues/1620, + https://github.com/fmtlib/fmt/issues/1622, + https://github.com/fmtlib/fmt/issues/1625, + https://github.com/fmtlib/fmt/pull/1627, + https://github.com/fmtlib/fmt/issues/1628, + https://github.com/fmtlib/fmt/pull/1629, + https://github.com/fmtlib/fmt/issues/1631, + https://github.com/fmtlib/fmt/pull/1633, + https://github.com/fmtlib/fmt/pull/1649, + https://github.com/fmtlib/fmt/issues/1658, + https://github.com/fmtlib/fmt/pull/1661, + https://github.com/fmtlib/fmt/pull/1667, + https://github.com/fmtlib/fmt/issues/1668, + https://github.com/fmtlib/fmt/pull/1669, + https://github.com/fmtlib/fmt/issues/1692, + https://github.com/fmtlib/fmt/pull/1696, + https://github.com/fmtlib/fmt/pull/1697, + https://github.com/fmtlib/fmt/issues/1707, + https://github.com/fmtlib/fmt/pull/1712, + https://github.com/fmtlib/fmt/pull/1716, + https://github.com/fmtlib/fmt/pull/1722, + https://github.com/fmtlib/fmt/issues/1724, + https://github.com/fmtlib/fmt/pull/1729, + https://github.com/fmtlib/fmt/pull/1738, + https://github.com/fmtlib/fmt/issues/1742, + https://github.com/fmtlib/fmt/issues/1743, + https://github.com/fmtlib/fmt/pull/1744, + https://github.com/fmtlib/fmt/issues/1747, + https://github.com/fmtlib/fmt/pull/1750). + Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10, + @daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink, + @tohammer and @0x8000-0000. + +# 6.2.1 - 2020-05-09 + +- Fixed ostream support in `sprintf` + (https://github.com/fmtlib/fmt/issues/1631). +- Fixed type detection when using implicit conversion to `string_view` + and ostream `operator<<` inconsistently + (https://github.com/fmtlib/fmt/issues/1662). + +# 6.2.0 - 2020-04-05 + +- Improved error reporting when trying to format an object of a + non-formattable type: + + ```c++ + fmt::format("{}", S()); + ``` + + now gives: + + include/fmt/core.h:1015:5: error: static_assert failed due to requirement + 'formattable' "Cannot format argument. To make type T formattable provide a + formatter specialization: + https://fmt.dev/latest/api.html#formatting-user-defined-types" + static_assert( + ^ + ... + note: in instantiation of function template specialization + 'fmt::v6::format' requested here + fmt::format("{}", S()); + ^ + + if `S` is not formattable. + +- Reduced the library size by \~10%. + +- Always print decimal point if `#` is specified + (https://github.com/fmtlib/fmt/issues/1476, + https://github.com/fmtlib/fmt/issues/1498): + + ```c++ + fmt::print("{:#.0f}", 42.0); + ``` + + now prints `42.` + +- Implemented the `'L'` specifier for locale-specific numeric + formatting to improve compatibility with `std::format`. The `'n'` + specifier is now deprecated and will be removed in the next major + release. + +- Moved OS-specific APIs such as `windows_error` from `fmt/format.h` + to `fmt/os.h`. You can define `FMT_DEPRECATED_INCLUDE_OS` to + automatically include `fmt/os.h` from `fmt/format.h` for + compatibility but this will be disabled in the next major release. + +- Added precision overflow detection in floating-point formatting. + +- Implemented detection of invalid use of `fmt::arg`. + +- Used `type_identity` to block unnecessary template argument + deduction. Thanks Tim Song. + +- Improved UTF-8 handling + (https://github.com/fmtlib/fmt/issues/1109): + + ```c++ + fmt::print("┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Прывітанне, свет!", 21); + ``` + + now prints: + + ┌─────────────────────┐ + │ Прывітанне, свет! │ + └─────────────────────┘ + + on systems that support Unicode. + +- Added experimental dynamic argument storage + (https://github.com/fmtlib/fmt/issues/1170, + https://github.com/fmtlib/fmt/pull/1584): + + ```c++ + fmt::dynamic_format_arg_store store; + store.push_back("answer"); + store.push_back(42); + fmt::vprint("The {} is {}.\n", store); + ``` + + prints: + + The answer is 42. + + Thanks @vsolontsov-ll. + +- Made `fmt::join` accept `initializer_list` + (https://github.com/fmtlib/fmt/pull/1591). Thanks @Rapotkinnik. + +- Fixed handling of empty tuples + (https://github.com/fmtlib/fmt/issues/1588). + +- Fixed handling of output iterators in `format_to_n` + (https://github.com/fmtlib/fmt/issues/1506). + +- Fixed formatting of `std::chrono::duration` types to wide output + (https://github.com/fmtlib/fmt/pull/1533). Thanks @zeffy. + +- Added const `begin` and `end` overload to buffers + (https://github.com/fmtlib/fmt/pull/1553). Thanks @dominicpoeschko. + +- Added the ability to disable floating-point formatting via + `FMT_USE_FLOAT`, `FMT_USE_DOUBLE` and `FMT_USE_LONG_DOUBLE` macros + for extremely memory-constrained embedded system + (https://github.com/fmtlib/fmt/pull/1590). Thanks @albaguirre. + +- Made `FMT_STRING` work with `constexpr` `string_view` + (https://github.com/fmtlib/fmt/pull/1589). Thanks @scramsby. + +- Implemented a minor optimization in the format string parser + (https://github.com/fmtlib/fmt/pull/1560). Thanks @IkarusDeveloper. + +- Improved attribute detection + (https://github.com/fmtlib/fmt/pull/1469, + https://github.com/fmtlib/fmt/pull/1475, + https://github.com/fmtlib/fmt/pull/1576). + Thanks @federico-busato, @chronoxor and @refnum. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/1481, + https://github.com/fmtlib/fmt/pull/1523). + Thanks @JackBoosY and @imba-tjd. + +- Fixed symbol visibility on Linux when compiling with + `-fvisibility=hidden` + (https://github.com/fmtlib/fmt/pull/1535). Thanks @milianw. + +- Implemented various build configuration fixes and improvements + (https://github.com/fmtlib/fmt/issues/1264, + https://github.com/fmtlib/fmt/issues/1460, + https://github.com/fmtlib/fmt/pull/1534, + https://github.com/fmtlib/fmt/issues/1536, + https://github.com/fmtlib/fmt/issues/1545, + https://github.com/fmtlib/fmt/pull/1546, + https://github.com/fmtlib/fmt/issues/1566, + https://github.com/fmtlib/fmt/pull/1582, + https://github.com/fmtlib/fmt/issues/1597, + https://github.com/fmtlib/fmt/pull/1598). + Thanks @ambitslix, @jwillikers and @stac47. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/1433, + https://github.com/fmtlib/fmt/issues/1461, + https://github.com/fmtlib/fmt/pull/1470, + https://github.com/fmtlib/fmt/pull/1480, + https://github.com/fmtlib/fmt/pull/1485, + https://github.com/fmtlib/fmt/pull/1492, + https://github.com/fmtlib/fmt/issues/1493, + https://github.com/fmtlib/fmt/issues/1504, + https://github.com/fmtlib/fmt/pull/1505, + https://github.com/fmtlib/fmt/pull/1512, + https://github.com/fmtlib/fmt/issues/1515, + https://github.com/fmtlib/fmt/pull/1516, + https://github.com/fmtlib/fmt/pull/1518, + https://github.com/fmtlib/fmt/pull/1519, + https://github.com/fmtlib/fmt/pull/1520, + https://github.com/fmtlib/fmt/pull/1521, + https://github.com/fmtlib/fmt/pull/1522, + https://github.com/fmtlib/fmt/issues/1524, + https://github.com/fmtlib/fmt/pull/1530, + https://github.com/fmtlib/fmt/issues/1531, + https://github.com/fmtlib/fmt/pull/1532, + https://github.com/fmtlib/fmt/issues/1539, + https://github.com/fmtlib/fmt/issues/1547, + https://github.com/fmtlib/fmt/issues/1548, + https://github.com/fmtlib/fmt/pull/1554, + https://github.com/fmtlib/fmt/issues/1567, + https://github.com/fmtlib/fmt/pull/1568, + https://github.com/fmtlib/fmt/pull/1569, + https://github.com/fmtlib/fmt/pull/1571, + https://github.com/fmtlib/fmt/pull/1573, + https://github.com/fmtlib/fmt/pull/1575, + https://github.com/fmtlib/fmt/pull/1581, + https://github.com/fmtlib/fmt/issues/1583, + https://github.com/fmtlib/fmt/issues/1586, + https://github.com/fmtlib/fmt/issues/1587, + https://github.com/fmtlib/fmt/issues/1594, + https://github.com/fmtlib/fmt/pull/1596, + https://github.com/fmtlib/fmt/issues/1604, + https://github.com/fmtlib/fmt/pull/1606, + https://github.com/fmtlib/fmt/issues/1607, + https://github.com/fmtlib/fmt/issues/1609). + Thanks @marti4d, @iPherian, @parkertomatoes, @gsjaardema, @chronoxor, + @DanielaE, @torsten48, @tohammer, @lefticus, @ryusakki, @adnsv, @fghzxm, + @refnum, @pramodk, @Spirrwell and @scramsby. + +# 6.1.2 - 2019-12-11 + +- Fixed ABI compatibility with `libfmt.so.6.0.0` + (https://github.com/fmtlib/fmt/issues/1471). +- Fixed handling types convertible to `std::string_view` + (https://github.com/fmtlib/fmt/pull/1451). Thanks @denizevrenci. +- Made CUDA test an opt-in enabled via the `FMT_CUDA_TEST` CMake + option. +- Fixed sign conversion warnings + (https://github.com/fmtlib/fmt/pull/1440). Thanks @0x8000-0000. + +# 6.1.1 - 2019-12-04 + +- Fixed shared library build on Windows + (https://github.com/fmtlib/fmt/pull/1443, + https://github.com/fmtlib/fmt/issues/1445, + https://github.com/fmtlib/fmt/pull/1446, + https://github.com/fmtlib/fmt/issues/1450). + Thanks @egorpugin and @bbolli. +- Added a missing decimal point in exponent notation with trailing + zeros. +- Removed deprecated `format_arg_store::TYPES`. + +# 6.1.0 - 2019-12-01 + +- {fmt} now formats IEEE 754 `float` and `double` using the shortest + decimal representation with correct rounding by default: + + ```c++ + #include + #include + + int main() { + fmt::print("{}", M_PI); + } + ``` + + prints `3.141592653589793`. + +- Made the fast binary to decimal floating-point formatter the + default, simplified it and improved performance. {fmt} is now 15 + times faster than libc++\'s `std::ostringstream`, 11 times faster + than `printf` and 10% faster than double-conversion on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark): + + | Function | Time (ns) | Speedup | + | ------------- | --------: | ------: | + | ostringstream | 1,346.30 | 1.00x | + | ostrstream | 1,195.74 | 1.13x | + | sprintf | 995.08 | 1.35x | + | doubleconv | 99.10 | 13.59x | + | fmt | 88.34 | 15.24x | + + ![](https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png) + +- {fmt} no longer converts `float` arguments to `double`. In + particular this improves the default (shortest) representation of + floats and makes `fmt::format` consistent with `std::format` specs + (https://github.com/fmtlib/fmt/issues/1336, + https://github.com/fmtlib/fmt/issues/1353, + https://github.com/fmtlib/fmt/pull/1360, + https://github.com/fmtlib/fmt/pull/1361): + + ```c++ + fmt::print("{}", 0.1f); + ``` + + prints `0.1` instead of `0.10000000149011612`. + + Thanks @orivej. + +- Made floating-point formatting output consistent with + `printf`/iostreams + (https://github.com/fmtlib/fmt/issues/1376, + https://github.com/fmtlib/fmt/issues/1417). + +- Added support for 128-bit integers + (https://github.com/fmtlib/fmt/pull/1287): + + ```c++ + fmt::print("{}", std::numeric_limits<__int128_t>::max()); + ``` + + prints `170141183460469231731687303715884105727`. + + Thanks @denizevrenci. + +- The overload of `print` that takes `text_style` is now atomic, i.e. + the output from different threads doesn\'t interleave + (https://github.com/fmtlib/fmt/pull/1351). Thanks @tankiJong. + +- Made compile time in the header-only mode \~20% faster by reducing + the number of template instantiations. `wchar_t` overload of + `vprint` was moved from `fmt/core.h` to `fmt/format.h`. + +- Added an overload of `fmt::join` that works with tuples + (https://github.com/fmtlib/fmt/issues/1322, + https://github.com/fmtlib/fmt/pull/1330): + + ```c++ + #include + #include + + int main() { + std::tuple t{'a', 1, 2.0f}; + fmt::print("{}", t); + } + ``` + + prints `('a', 1, 2.0)`. + + Thanks @jeremyong. + +- Changed formatting of octal zero with prefix from \"00\" to \"0\": + + ```c++ + fmt::print("{:#o}", 0); + ``` + + prints `0`. + +- The locale is now passed to ostream insertion (`<<`) operators + (https://github.com/fmtlib/fmt/pull/1406): + + ```c++ + #include + #include + + struct S { + double value; + }; + + std::ostream& operator<<(std::ostream& os, S s) { + return os << s.value; + } + + int main() { + auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42}); + // s == "0,42" + } + ``` + + Thanks @dlaugt. + +- Locale-specific number formatting now uses grouping + (https://github.com/fmtlib/fmt/issues/1393, + https://github.com/fmtlib/fmt/pull/1394). Thanks @skrdaniel. + +- Fixed handling of types with deleted implicit rvalue conversion to + `const char**` (https://github.com/fmtlib/fmt/issues/1421): + + ```c++ + struct mystring { + operator const char*() const&; + operator const char*() &; + operator const char*() const&& = delete; + operator const char*() && = delete; + }; + mystring str; + fmt::print("{}", str); // now compiles + ``` + +- Enums are now mapped to correct underlying types instead of `int` + (https://github.com/fmtlib/fmt/pull/1286). Thanks @agmt. + +- Enum classes are no longer implicitly converted to `int` + (https://github.com/fmtlib/fmt/issues/1424). + +- Added `basic_format_parse_context` for consistency with C++20 + `std::format` and deprecated `basic_parse_context`. + +- Fixed handling of UTF-8 in precision + (https://github.com/fmtlib/fmt/issues/1389, + https://github.com/fmtlib/fmt/pull/1390). Thanks @tajtiattila. + +- {fmt} can now be installed on Linux, macOS and Windows with + [Conda](https://docs.conda.io/en/latest/) using its + [conda-forge](https://conda-forge.org) + [package](https://github.com/conda-forge/fmt-feedstock) + (https://github.com/fmtlib/fmt/pull/1410): + + conda install -c conda-forge fmt + + Thanks @tdegeus. + +- Added a CUDA test (https://github.com/fmtlib/fmt/pull/1285, + https://github.com/fmtlib/fmt/pull/1317). + Thanks @luncliff and @risa2000. + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/1276, + https://github.com/fmtlib/fmt/issues/1291, + https://github.com/fmtlib/fmt/issues/1296, + https://github.com/fmtlib/fmt/pull/1315, + https://github.com/fmtlib/fmt/pull/1332, + https://github.com/fmtlib/fmt/pull/1337, + https://github.com/fmtlib/fmt/issues/1395 + https://github.com/fmtlib/fmt/pull/1418). + Thanks @waywardmonkeys, @pauldreik and @jackoalan. + +- Various code improvements + (https://github.com/fmtlib/fmt/pull/1358, + https://github.com/fmtlib/fmt/pull/1407). + Thanks @orivej and @dpacbach. + +- Fixed compile-time format string checks for user-defined types + (https://github.com/fmtlib/fmt/issues/1292). + +- Worked around a false positive in `unsigned-integer-overflow` sanitizer + (https://github.com/fmtlib/fmt/issues/1377). + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/1273, + https://github.com/fmtlib/fmt/pull/1278, + https://github.com/fmtlib/fmt/pull/1280, + https://github.com/fmtlib/fmt/issues/1281, + https://github.com/fmtlib/fmt/issues/1288, + https://github.com/fmtlib/fmt/pull/1290, + https://github.com/fmtlib/fmt/pull/1301, + https://github.com/fmtlib/fmt/issues/1305, + https://github.com/fmtlib/fmt/issues/1306, + https://github.com/fmtlib/fmt/issues/1309, + https://github.com/fmtlib/fmt/pull/1312, + https://github.com/fmtlib/fmt/issues/1313, + https://github.com/fmtlib/fmt/issues/1316, + https://github.com/fmtlib/fmt/issues/1319, + https://github.com/fmtlib/fmt/pull/1320, + https://github.com/fmtlib/fmt/pull/1326, + https://github.com/fmtlib/fmt/pull/1328, + https://github.com/fmtlib/fmt/issues/1344, + https://github.com/fmtlib/fmt/pull/1345, + https://github.com/fmtlib/fmt/pull/1347, + https://github.com/fmtlib/fmt/pull/1349, + https://github.com/fmtlib/fmt/issues/1354, + https://github.com/fmtlib/fmt/issues/1362, + https://github.com/fmtlib/fmt/issues/1366, + https://github.com/fmtlib/fmt/pull/1364, + https://github.com/fmtlib/fmt/pull/1370, + https://github.com/fmtlib/fmt/pull/1371, + https://github.com/fmtlib/fmt/issues/1385, + https://github.com/fmtlib/fmt/issues/1388, + https://github.com/fmtlib/fmt/pull/1397, + https://github.com/fmtlib/fmt/pull/1414, + https://github.com/fmtlib/fmt/pull/1416, + https://github.com/fmtlib/fmt/issues/1422 + https://github.com/fmtlib/fmt/pull/1427, + https://github.com/fmtlib/fmt/issues/1431, + https://github.com/fmtlib/fmt/pull/1433). + Thanks @hhb, @gsjaardema, @gabime, @neheb, @vedranmiletic, @dkavolis, + @mwinterb, @orivej, @denizevrenci, @leonklingele, @chronoxor, @kent-tri, + @0x8000-0000 and @marti4d. + +# 6.0.0 - 2019-08-26 + +- Switched to the [MIT license]( + https://github.com/fmtlib/fmt/blob/5a4b24613ba16cc689977c3b5bd8274a3ba1dd1f/LICENSE.rst) + with an optional exception that allows distributing binary code + without attribution. + +- Floating-point formatting is now locale-independent by default: + + ```c++ + #include + #include + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {}", 4.2); + } + ``` + + prints \"value = 4.2\" regardless of the locale. + + For locale-specific formatting use the `n` specifier: + + ```c++ + std::locale::global(std::locale("ru_RU.UTF-8")); + fmt::print("value = {:n}", 4.2); + ``` + + prints \"value = 4,2\". + +- Added an experimental Grisu floating-point formatting algorithm + implementation (disabled by default). To enable it compile with the + `FMT_USE_GRISU` macro defined to 1: + + ```c++ + #define FMT_USE_GRISU 1 + #include + + auto s = fmt::format("{}", 4.2); // formats 4.2 using Grisu + ``` + + With Grisu enabled, {fmt} is 13x faster than `std::ostringstream` + (libc++) and 10x faster than `sprintf` on + [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark) ([full + results](https://fmt.dev/unknown_mac64_clang10.0.html)): + + ![](https://user-images.githubusercontent.com/576385/54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg) + +- Separated formatting and parsing contexts for consistency with + [C++20 std::format](http://eel.is/c++draft/format), removing the + undocumented `basic_format_context::parse_context()` function. + +- Added [oss-fuzz](https://github.com/google/oss-fuzz) support + (https://github.com/fmtlib/fmt/pull/1199). Thanks @pauldreik. + +- `formatter` specializations now always take precedence over + `operator<<` (https://github.com/fmtlib/fmt/issues/952): + + ```c++ + #include + #include + + struct S {}; + + std::ostream& operator<<(std::ostream& os, S) { + return os << 1; + } + + template <> + struct fmt::formatter : fmt::formatter { + auto format(S, format_context& ctx) { + return formatter::format(2, ctx); + } + }; + + int main() { + std::cout << S() << "\n"; // prints 1 using operator<< + fmt::print("{}\n", S()); // prints 2 using formatter + } + ``` + +- Introduced the experimental `fmt::compile` function that does format + string compilation + (https://github.com/fmtlib/fmt/issues/618, + https://github.com/fmtlib/fmt/issues/1169, + https://github.com/fmtlib/fmt/pull/1171): + + ```c++ + #include + + auto f = fmt::compile("{}"); + std::string s = fmt::format(f, 42); // can be called multiple times to + // format different values + // s == "42" + ``` + + It moves the cost of parsing a format string outside of the format + function which can be beneficial when identically formatting many + objects of the same types. Thanks @stryku. + +- Added experimental `%` format specifier that formats floating-point + values as percentages + (https://github.com/fmtlib/fmt/pull/1060, + https://github.com/fmtlib/fmt/pull/1069, + https://github.com/fmtlib/fmt/pull/1071): + + ```c++ + auto s = fmt::format("{:.1%}", 0.42); // s == "42.0%" + ``` + + Thanks @gawain-bolton. + +- Implemented precision for floating-point durations + (https://github.com/fmtlib/fmt/issues/1004, + https://github.com/fmtlib/fmt/pull/1012): + + ```c++ + auto s = fmt::format("{:.1}", std::chrono::duration(1.234)); + // s == 1.2s + ``` + + Thanks @DanielaE. + +- Implemented `chrono` format specifiers `%Q` and `%q` that give the + value and the unit respectively + (https://github.com/fmtlib/fmt/pull/1019): + + ```c++ + auto value = fmt::format("{:%Q}", 42s); // value == "42" + auto unit = fmt::format("{:%q}", 42s); // unit == "s" + ``` + + Thanks @DanielaE. + +- Fixed handling of dynamic width in chrono formatter: + + ```c++ + auto s = fmt::format("{0:{1}%H:%M:%S}", std::chrono::seconds(12345), 12); + // ^ width argument index ^ width + // s == "03:25:45 " + ``` + + Thanks Howard Hinnant. + +- Removed deprecated `fmt/time.h`. Use `fmt/chrono.h` instead. + +- Added `fmt::format` and `fmt::vformat` overloads that take + `text_style` (https://github.com/fmtlib/fmt/issues/993, + https://github.com/fmtlib/fmt/pull/994): + + ```c++ + #include + + std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + ``` + + Thanks @Naios. + +- Removed the deprecated color API (`print_colored`). Use the new API, + namely `print` overloads that take `text_style` instead. + +- Made `std::unique_ptr` and `std::shared_ptr` formattable as pointers + via `fmt::ptr` (https://github.com/fmtlib/fmt/pull/1121): + + ```c++ + std::unique_ptr p = ...; + fmt::print("{}", fmt::ptr(p)); // prints p as a pointer + ``` + + Thanks @sighingnow. + +- Made `print` and `vprint` report I/O errors + (https://github.com/fmtlib/fmt/issues/1098, + https://github.com/fmtlib/fmt/pull/1099). Thanks @BillyDonahue. + +- Marked deprecated APIs with the `[[deprecated]]` attribute and + removed internal uses of deprecated APIs + (https://github.com/fmtlib/fmt/pull/1022). Thanks @eliaskosunen. + +- Modernized the codebase using more C++11 features and removing + workarounds. Most importantly, `buffer_context` is now an alias + template, so use `buffer_context` instead of + `buffer_context::type`. These features require GCC 4.8 or later. + +- `formatter` specializations now always take precedence over implicit + conversions to `int` and the undocumented `convert_to_int` trait is + now deprecated. + +- Moved the undocumented `basic_writer`, `writer`, and `wwriter` types + to the `internal` namespace. + +- Removed deprecated `basic_format_context::begin()`. Use `out()` + instead. + +- Disallowed passing the result of `join` as an lvalue to prevent + misuse. + +- Refactored the undocumented structs that represent parsed format + specifiers to simplify the API and allow multibyte fill. + +- Moved SFINAE to template parameters to reduce symbol sizes. + +- Switched to `fputws` for writing wide strings so that it\'s no + longer required to call `_setmode` on Windows + (https://github.com/fmtlib/fmt/issues/1229, + https://github.com/fmtlib/fmt/pull/1243). Thanks @jackoalan. + +- Improved literal-based API + (https://github.com/fmtlib/fmt/pull/1254). Thanks @sylveon. + +- Added support for exotic platforms without `uintptr_t` such as IBM i + (AS/400) which has 128-bit pointers and only 64-bit integers + (https://github.com/fmtlib/fmt/issues/1059). + +- Added [Sublime Text syntax highlighting config]( + https://github.com/fmtlib/fmt/blob/master/support/C%2B%2B.sublime-syntax) + (https://github.com/fmtlib/fmt/issues/1037). Thanks @Kronuz. + +- Added the `FMT_ENFORCE_COMPILE_STRING` macro to enforce the use of + compile-time format strings + (https://github.com/fmtlib/fmt/pull/1231). Thanks @jackoalan. + +- Stopped setting `CMAKE_BUILD_TYPE` if {fmt} is a subproject + (https://github.com/fmtlib/fmt/issues/1081). + +- Various build improvements + (https://github.com/fmtlib/fmt/pull/1039, + https://github.com/fmtlib/fmt/pull/1078, + https://github.com/fmtlib/fmt/pull/1091, + https://github.com/fmtlib/fmt/pull/1103, + https://github.com/fmtlib/fmt/pull/1177). + Thanks @luncliff, @jasonszang, @olafhering, @Lecetem and @pauldreik. + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/1049, + https://github.com/fmtlib/fmt/pull/1051, + https://github.com/fmtlib/fmt/pull/1083, + https://github.com/fmtlib/fmt/pull/1113, + https://github.com/fmtlib/fmt/pull/1114, + https://github.com/fmtlib/fmt/issues/1146, + https://github.com/fmtlib/fmt/issues/1180, + https://github.com/fmtlib/fmt/pull/1250, + https://github.com/fmtlib/fmt/pull/1252, + https://github.com/fmtlib/fmt/pull/1265). + Thanks @mikelui, @foonathan, @BillyDonahue, @jwakely, @kaisbe and + @sdebionne. + +- Fixed ambiguous formatter specialization in `fmt/ranges.h` + (https://github.com/fmtlib/fmt/issues/1123). + +- Fixed formatting of a non-empty `std::filesystem::path` which is an + infinitely deep range of its components + (https://github.com/fmtlib/fmt/issues/1268). + +- Fixed handling of general output iterators when formatting + characters (https://github.com/fmtlib/fmt/issues/1056, + https://github.com/fmtlib/fmt/pull/1058). Thanks @abolz. + +- Fixed handling of output iterators in `formatter` specialization for + ranges (https://github.com/fmtlib/fmt/issues/1064). + +- Fixed handling of exotic character types + (https://github.com/fmtlib/fmt/issues/1188). + +- Made chrono formatting work with exceptions disabled + (https://github.com/fmtlib/fmt/issues/1062). + +- Fixed DLL visibility issues + (https://github.com/fmtlib/fmt/pull/1134, + https://github.com/fmtlib/fmt/pull/1147). Thanks @denchat. + +- Disabled the use of UDL template extension on GCC 9 + (https://github.com/fmtlib/fmt/issues/1148). + +- Removed misplaced `format` compile-time checks from `printf` + (https://github.com/fmtlib/fmt/issues/1173). + +- Fixed issues in the experimental floating-point formatter + (https://github.com/fmtlib/fmt/issues/1072, + https://github.com/fmtlib/fmt/issues/1129, + https://github.com/fmtlib/fmt/issues/1153, + https://github.com/fmtlib/fmt/pull/1155, + https://github.com/fmtlib/fmt/issues/1210, + https://github.com/fmtlib/fmt/issues/1222). Thanks @alabuzhev. + +- Fixed bugs discovered by fuzzing or during fuzzing integration + (https://github.com/fmtlib/fmt/issues/1124, + https://github.com/fmtlib/fmt/issues/1127, + https://github.com/fmtlib/fmt/issues/1132, + https://github.com/fmtlib/fmt/pull/1135, + https://github.com/fmtlib/fmt/issues/1136, + https://github.com/fmtlib/fmt/issues/1141, + https://github.com/fmtlib/fmt/issues/1142, + https://github.com/fmtlib/fmt/issues/1178, + https://github.com/fmtlib/fmt/issues/1179, + https://github.com/fmtlib/fmt/issues/1194). Thanks @pauldreik. + +- Fixed building tests on FreeBSD and Hurd + (https://github.com/fmtlib/fmt/issues/1043). Thanks @jackyf. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/pull/998, + https://github.com/fmtlib/fmt/pull/1006, + https://github.com/fmtlib/fmt/issues/1008, + https://github.com/fmtlib/fmt/issues/1011, + https://github.com/fmtlib/fmt/issues/1025, + https://github.com/fmtlib/fmt/pull/1027, + https://github.com/fmtlib/fmt/pull/1028, + https://github.com/fmtlib/fmt/pull/1029, + https://github.com/fmtlib/fmt/pull/1030, + https://github.com/fmtlib/fmt/pull/1031, + https://github.com/fmtlib/fmt/pull/1054, + https://github.com/fmtlib/fmt/issues/1063, + https://github.com/fmtlib/fmt/pull/1068, + https://github.com/fmtlib/fmt/pull/1074, + https://github.com/fmtlib/fmt/pull/1075, + https://github.com/fmtlib/fmt/pull/1079, + https://github.com/fmtlib/fmt/pull/1086, + https://github.com/fmtlib/fmt/issues/1088, + https://github.com/fmtlib/fmt/pull/1089, + https://github.com/fmtlib/fmt/pull/1094, + https://github.com/fmtlib/fmt/issues/1101, + https://github.com/fmtlib/fmt/pull/1102, + https://github.com/fmtlib/fmt/issues/1105, + https://github.com/fmtlib/fmt/pull/1107, + https://github.com/fmtlib/fmt/issues/1115, + https://github.com/fmtlib/fmt/issues/1117, + https://github.com/fmtlib/fmt/issues/1118, + https://github.com/fmtlib/fmt/issues/1120, + https://github.com/fmtlib/fmt/issues/1123, + https://github.com/fmtlib/fmt/pull/1139, + https://github.com/fmtlib/fmt/issues/1140, + https://github.com/fmtlib/fmt/issues/1143, + https://github.com/fmtlib/fmt/pull/1144, + https://github.com/fmtlib/fmt/pull/1150, + https://github.com/fmtlib/fmt/pull/1151, + https://github.com/fmtlib/fmt/issues/1152, + https://github.com/fmtlib/fmt/issues/1154, + https://github.com/fmtlib/fmt/issues/1156, + https://github.com/fmtlib/fmt/pull/1159, + https://github.com/fmtlib/fmt/issues/1175, + https://github.com/fmtlib/fmt/issues/1181, + https://github.com/fmtlib/fmt/issues/1186, + https://github.com/fmtlib/fmt/pull/1187, + https://github.com/fmtlib/fmt/pull/1191, + https://github.com/fmtlib/fmt/issues/1197, + https://github.com/fmtlib/fmt/issues/1200, + https://github.com/fmtlib/fmt/issues/1203, + https://github.com/fmtlib/fmt/issues/1205, + https://github.com/fmtlib/fmt/pull/1206, + https://github.com/fmtlib/fmt/issues/1213, + https://github.com/fmtlib/fmt/issues/1214, + https://github.com/fmtlib/fmt/pull/1217, + https://github.com/fmtlib/fmt/issues/1228, + https://github.com/fmtlib/fmt/pull/1230, + https://github.com/fmtlib/fmt/issues/1232, + https://github.com/fmtlib/fmt/pull/1235, + https://github.com/fmtlib/fmt/pull/1236, + https://github.com/fmtlib/fmt/issues/1240). + Thanks @DanielaE, @mwinterb, @eliaskosunen, @morinmorin, @ricco19, + @waywardmonkeys, @chronoxor, @remyabel, @pauldreik, @gsjaardema, @rcane, + @mocabe, @denchat, @cjdb, @HazardyKnusperkeks, @vedranmiletic, @jackoalan, + @DaanDeMeyer and @starkmapper. + +# 5.3.0 - 2018-12-28 + +- Introduced experimental chrono formatting support: + + ```c++ + #include + + int main() { + using namespace std::literals::chrono_literals; + fmt::print("Default format: {} {}\n", 42s, 100ms); + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + ``` + + prints: + + Default format: 42s 100ms + strftime-like format: 03:15:30 + +- Added experimental support for emphasis (bold, italic, underline, + strikethrough), colored output to a file stream, and improved + colored formatting API + (https://github.com/fmtlib/fmt/pull/961, + https://github.com/fmtlib/fmt/pull/967, + https://github.com/fmtlib/fmt/pull/973): + + ```c++ + #include + + int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "你好{}!\n", "世界"); + } + ``` + + prints the following on modern terminals with RGB color support: + + ![](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) + + Thanks @Rakete1111. + +- Added support for 4-bit terminal colors + (https://github.com/fmtlib/fmt/issues/968, + https://github.com/fmtlib/fmt/pull/974) + + ```c++ + #include + + int main() { + print(fg(fmt::terminal_color::red), "stop\n"); + } + ``` + + Note that these colors vary by terminal: + + ![](https://user-images.githubusercontent.com/576385/50405925-dbfc7e00-0770-11e9-9b85-333fab0af9ac.png) + + Thanks @Rakete1111. + +- Parameterized formatting functions on the type of the format string + (https://github.com/fmtlib/fmt/issues/880, + https://github.com/fmtlib/fmt/pull/881, + https://github.com/fmtlib/fmt/pull/883, + https://github.com/fmtlib/fmt/pull/885, + https://github.com/fmtlib/fmt/pull/897, + https://github.com/fmtlib/fmt/issues/920). Any object of + type `S` that has an overloaded `to_string_view(const S&)` returning + `fmt::string_view` can be used as a format string: + + ```c++ + namespace my_ns { + inline string_view to_string_view(const my_string& s) { + return {s.data(), s.length()}; + } + } + + std::string message = fmt::format(my_string("The answer is {}."), 42); + ``` + + Thanks @DanielaE. + +- Made `std::string_view` work as a format string + (https://github.com/fmtlib/fmt/pull/898): + + ```c++ + auto message = fmt::format(std::string_view("The answer is {}."), 42); + ``` + + Thanks @DanielaE. + +- Added wide string support to compile-time format string checks + (https://github.com/fmtlib/fmt/pull/924): + + ```c++ + print(fmt(L"{:f}"), 42); // compile-time error: invalid type specifier + ``` + + Thanks @XZiar. + +- Made colored print functions work with wide strings + (https://github.com/fmtlib/fmt/pull/867): + + ```c++ + #include + + int main() { + print(fg(fmt::color::red), L"{}\n", 42); + } + ``` + + Thanks @DanielaE. + +- Introduced experimental Unicode support + (https://github.com/fmtlib/fmt/issues/628, + https://github.com/fmtlib/fmt/pull/891): + + ```c++ + using namespace fmt::literals; + auto s = fmt::format("{:*^5}"_u, "🤡"_u); // s == "**🤡**"_u + ``` + +- Improved locale support: + + ```c++ + #include + + struct numpunct : std::numpunct { + protected: + char do_thousands_sep() const override { return '~'; } + }; + + std::locale loc; + auto s = fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567); + // s == "1~234~567" + ``` + +- Constrained formatting functions on proper iterator types + (https://github.com/fmtlib/fmt/pull/921). Thanks @DanielaE. + +- Added `make_printf_args` and `make_wprintf_args` functions + (https://github.com/fmtlib/fmt/pull/934). Thanks @tnovotny. + +- Deprecated `fmt::visit`, `parse_context`, and `wparse_context`. Use + `fmt::visit_format_arg`, `format_parse_context`, and + `wformat_parse_context` instead. + +- Removed undocumented `basic_fixed_buffer` which has been superseded + by the iterator-based API + (https://github.com/fmtlib/fmt/issues/873, + https://github.com/fmtlib/fmt/pull/902). Thanks @superfunc. + +- Disallowed repeated leading zeros in an argument ID: + + ```c++ + fmt::print("{000}", 42); // error + ``` + +- Reintroduced support for gcc 4.4. + +- Fixed compilation on platforms with exotic `double` + (https://github.com/fmtlib/fmt/issues/878). + +- Improved documentation + (https://github.com/fmtlib/fmt/issues/164, + https://github.com/fmtlib/fmt/issues/877, + https://github.com/fmtlib/fmt/pull/901, + https://github.com/fmtlib/fmt/pull/906, + https://github.com/fmtlib/fmt/pull/979). + Thanks @kookjr, @DarkDimius and @HecticSerenity. + +- Added pkgconfig support which makes it easier to consume the library + from meson and other build systems + (https://github.com/fmtlib/fmt/pull/916). Thanks @colemickens. + +- Various build improvements + (https://github.com/fmtlib/fmt/pull/909, + https://github.com/fmtlib/fmt/pull/926, + https://github.com/fmtlib/fmt/pull/937, + https://github.com/fmtlib/fmt/pull/953, + https://github.com/fmtlib/fmt/pull/959). + Thanks @tchaikov, @luncliff, @AndreasSchoenle, @hotwatermorning and @Zefz. + +- Improved `string_view` construction performance + (https://github.com/fmtlib/fmt/pull/914). Thanks @gabime. + +- Fixed non-matching char types + (https://github.com/fmtlib/fmt/pull/895). Thanks @DanielaE. + +- Fixed `format_to_n` with `std::back_insert_iterator` + (https://github.com/fmtlib/fmt/pull/913). Thanks @DanielaE. + +- Fixed locale-dependent formatting + (https://github.com/fmtlib/fmt/issues/905). + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/pull/882, + https://github.com/fmtlib/fmt/pull/886, + https://github.com/fmtlib/fmt/pull/933, + https://github.com/fmtlib/fmt/pull/941, + https://github.com/fmtlib/fmt/issues/931, + https://github.com/fmtlib/fmt/pull/943, + https://github.com/fmtlib/fmt/pull/954, + https://github.com/fmtlib/fmt/pull/956, + https://github.com/fmtlib/fmt/pull/962, + https://github.com/fmtlib/fmt/issues/965, + https://github.com/fmtlib/fmt/issues/977, + https://github.com/fmtlib/fmt/pull/983, + https://github.com/fmtlib/fmt/pull/989). + Thanks @Luthaf, @stevenhoving, @christinaa, @lgritz, @DanielaE, + @0x8000-0000 and @liuping1997. + +# 5.2.1 - 2018-09-21 + +- Fixed `visit` lookup issues on gcc 7 & 8 + (https://github.com/fmtlib/fmt/pull/870). Thanks @medithe. +- Fixed linkage errors on older gcc. +- Prevented `fmt/range.h` from specializing `fmt::basic_string_view` + (https://github.com/fmtlib/fmt/issues/865, + https://github.com/fmtlib/fmt/pull/868). Thanks @hhggit. +- Improved error message when formatting unknown types + (https://github.com/fmtlib/fmt/pull/872). Thanks @foonathan. +- Disabled templated user-defined literals when compiled under nvcc + (https://github.com/fmtlib/fmt/pull/875). Thanks @CandyGumdrop. +- Fixed `format_to` formatting to `wmemory_buffer` + (https://github.com/fmtlib/fmt/issues/874). + +# 5.2.0 - 2018-09-13 + +- Optimized format string parsing and argument processing which + resulted in up to 5x speed up on long format strings and significant + performance boost on various benchmarks. For example, version 5.2 is + 2.22x faster than 5.1 on decimal integer formatting with `format_to` + (macOS, clang-902.0.39.2): + + | Method | Time, s | Speedup | + | -------------------------- | --------------: | ------: | + | fmt::format 5.1 | 0.58 | | + | fmt::format 5.2 | 0.35 | 1.66x | + | fmt::format_to 5.1 | 0.51 | | + | fmt::format_to 5.2 | 0.23 | 2.22x | + | sprintf | 0.71 | | + | std::to_string | 1.01 | | + | std::stringstream | 1.73 | | + +- Changed the `fmt` macro from opt-out to opt-in to prevent name + collisions. To enable it define the `FMT_STRING_ALIAS` macro to 1 + before including `fmt/format.h`: + + ```c++ + #define FMT_STRING_ALIAS 1 + #include + std::string answer = format(fmt("{}"), 42); + ``` + +- Added compile-time format string checks to `format_to` overload that + takes `fmt::memory_buffer` + (https://github.com/fmtlib/fmt/issues/783): + + ```c++ + fmt::memory_buffer buf; + // Compile-time error: invalid type specifier. + fmt::format_to(buf, fmt("{:d}"), "foo"); + ``` + +- Moved experimental color support to `fmt/color.h` and enabled the + new API by default. The old API can be enabled by defining the + `FMT_DEPRECATED_COLORS` macro. + +- Added formatting support for types explicitly convertible to + `fmt::string_view`: + + ```c++ + struct foo { + explicit operator fmt::string_view() const { return "foo"; } + }; + auto s = format("{}", foo()); + ``` + + In particular, this makes formatting function work with + `folly::StringPiece`. + +- Implemented preliminary support for `char*_t` by replacing the + `format` function overloads with a single function template + parameterized on the string type. + +- Added support for dynamic argument lists + (https://github.com/fmtlib/fmt/issues/814, + https://github.com/fmtlib/fmt/pull/819). Thanks @MikePopoloski. + +- Reduced executable size overhead for embedded targets using newlib + nano by making locale dependency optional + (https://github.com/fmtlib/fmt/pull/839). Thanks @teajay-fr. + +- Keep `noexcept` specifier when exceptions are disabled + (https://github.com/fmtlib/fmt/issues/801, + https://github.com/fmtlib/fmt/pull/810). Thanks @qis. + +- Fixed formatting of user-defined types providing `operator<<` with + `format_to_n` (https://github.com/fmtlib/fmt/pull/806). + Thanks @mkurdej. + +- Fixed dynamic linkage of new symbols + (https://github.com/fmtlib/fmt/issues/808). + +- Fixed global initialization issue + (https://github.com/fmtlib/fmt/issues/807): + + ```c++ + // This works on compilers with constexpr support. + static const std::string answer = fmt::format("{}", 42); + ``` + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/pull/804, + https://github.com/fmtlib/fmt/issues/809, + https://github.com/fmtlib/fmt/pull/811, + https://github.com/fmtlib/fmt/issues/822, + https://github.com/fmtlib/fmt/pull/827, + https://github.com/fmtlib/fmt/issues/830, + https://github.com/fmtlib/fmt/pull/838, + https://github.com/fmtlib/fmt/issues/843, + https://github.com/fmtlib/fmt/pull/844, + https://github.com/fmtlib/fmt/issues/851, + https://github.com/fmtlib/fmt/pull/852, + https://github.com/fmtlib/fmt/pull/854). + Thanks @henryiii, @medithe, and @eliasdaler. + +# 5.1.0 - 2018-07-05 + +- Added experimental support for RGB color output enabled with the + `FMT_EXTENDED_COLORS` macro: + + ```c++ + #define FMT_EXTENDED_COLORS + #define FMT_HEADER_ONLY // or compile fmt with FMT_EXTENDED_COLORS defined + #include + + fmt::print(fmt::color::steel_blue, "Some beautiful text"); + ``` + + The old API (the `print_colored` and `vprint_colored` functions and + the `color` enum) is now deprecated. + (https://github.com/fmtlib/fmt/issues/762 + https://github.com/fmtlib/fmt/pull/767). thanks @Remotion. + +- Added quotes to strings in ranges and tuples + (https://github.com/fmtlib/fmt/pull/766). Thanks @Remotion. + +- Made `format_to` work with `basic_memory_buffer` + (https://github.com/fmtlib/fmt/issues/776). + +- Added `vformat_to_n` and `wchar_t` overload of `format_to_n` + (https://github.com/fmtlib/fmt/issues/764, + https://github.com/fmtlib/fmt/issues/769). + +- Made `is_range` and `is_tuple_like` part of public (experimental) + API to allow specialization for user-defined types + (https://github.com/fmtlib/fmt/issues/751, + https://github.com/fmtlib/fmt/pull/759). Thanks @drrlvn. + +- Added more compilers to continuous integration and increased + `FMT_PEDANTIC` warning levels + (https://github.com/fmtlib/fmt/pull/736). Thanks @eliaskosunen. + +- Fixed compilation with MSVC 2013. + +- Fixed handling of user-defined types in `format_to` + (https://github.com/fmtlib/fmt/issues/793). + +- Forced linking of inline `vformat` functions into the library + (https://github.com/fmtlib/fmt/issues/795). + +- Fixed incorrect call to on_align in `'{:}='` + (https://github.com/fmtlib/fmt/issues/750). + +- Fixed floating-point formatting to a non-back_insert_iterator with + sign & numeric alignment specified + (https://github.com/fmtlib/fmt/issues/756). + +- Fixed formatting to an array with `format_to_n` + (https://github.com/fmtlib/fmt/issues/778). + +- Fixed formatting of more than 15 named arguments + (https://github.com/fmtlib/fmt/issues/754). + +- Fixed handling of compile-time strings when including + `fmt/ostream.h`. (https://github.com/fmtlib/fmt/issues/768). + +- Fixed various compiler warnings and errors + (https://github.com/fmtlib/fmt/issues/742, + https://github.com/fmtlib/fmt/issues/748, + https://github.com/fmtlib/fmt/issues/752, + https://github.com/fmtlib/fmt/issues/770, + https://github.com/fmtlib/fmt/pull/775, + https://github.com/fmtlib/fmt/issues/779, + https://github.com/fmtlib/fmt/pull/780, + https://github.com/fmtlib/fmt/pull/790, + https://github.com/fmtlib/fmt/pull/792, + https://github.com/fmtlib/fmt/pull/800). + Thanks @Remotion, @gabime, @foonathan, @Dark-Passenger and @0x8000-0000. + +# 5.0.0 - 2018-05-21 + +- Added a requirement for partial C++11 support, most importantly + variadic templates and type traits, and dropped `FMT_VARIADIC_*` + emulation macros. Variadic templates are available since GCC 4.4, + Clang 2.9 and MSVC 18.0 (2013). For older compilers use {fmt} + [version 4.x](https://github.com/fmtlib/fmt/releases/tag/4.1.0) + which continues to be maintained and works with C++98 compilers. + +- Renamed symbols to follow standard C++ naming conventions and + proposed a subset of the library for standardization in [P0645R2 + Text Formatting](https://wg21.link/P0645). + +- Implemented `constexpr` parsing of format strings and [compile-time + format string + checks](https://fmt.dev/latest/api.html#compile-time-format-string-checks). + For example + + ```c++ + #include + + std::string s = format(fmt("{:d}"), "foo"); + ``` + + gives a compile-time error because `d` is an invalid specifier for + strings ([godbolt](https://godbolt.org/g/rnCy9Q)): + + ... + :4:19: note: in instantiation of function template specialization 'fmt::v5::format' requested here + std::string s = format(fmt("{:d}"), "foo"); + ^ + format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression + handler.on_error("invalid type specifier"); + + Compile-time checks require relaxed `constexpr` (C++14 feature) + support. If the latter is not available, checks will be performed at + runtime. + +- Separated format string parsing and formatting in the extension API + to enable compile-time format string processing. For example + + ```c++ + struct Answer {}; + + namespace fmt { + template <> + struct formatter { + constexpr auto parse(parse_context& ctx) { + auto it = ctx.begin(); + spec = *it; + if (spec != 'd' && spec != 's') + throw format_error("invalid specifier"); + return ++it; + } + + template + auto format(Answer, FormatContext& ctx) { + return spec == 's' ? + format_to(ctx.begin(), "{}", "fourty-two") : + format_to(ctx.begin(), "{}", 42); + } + + char spec = 0; + }; + } + + std::string s = format(fmt("{:x}"), Answer()); + ``` + + gives a compile-time error due to invalid format specifier + ([godbolt](https://godbolt.org/g/2jQ1Dv)): + + ... + :12:45: error: expression '' is not a constant expression + throw format_error("invalid specifier"); + +- Added [iterator + support](https://fmt.dev/latest/api.html#output-iterator-support): + + ```c++ + #include + #include + + std::vector out; + fmt::format_to(std::back_inserter(out), "{}", 42); + ``` + +- Added the + [format_to_n](https://fmt.dev/latest/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args) + function that restricts the output to the specified number of + characters (https://github.com/fmtlib/fmt/issues/298): + + ```c++ + char out[4]; + fmt::format_to_n(out, sizeof(out), "{}", 12345); + // out == "1234" (without terminating '\0') + ``` + +- Added the [formatted_size]( + https://fmt.dev/latest/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args) + function for computing the output size: + + ```c++ + #include + + auto size = fmt::formatted_size("{}", 12345); // size == 5 + ``` + +- Improved compile times by reducing dependencies on standard headers + and providing a lightweight [core + API](https://fmt.dev/latest/api.html#core-api): + + ```c++ + #include + + fmt::print("The answer is {}.", 42); + ``` + + See [Compile time and code + bloat](https://github.com/fmtlib/fmt#compile-time-and-code-bloat). + +- Added the [make_format_args]( + https://fmt.dev/latest/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args) + function for capturing formatting arguments: + + ```c++ + // Prints formatted error message. + void vreport_error(const char *format, fmt::format_args args) { + fmt::print("Error: "); + fmt::vprint(format, args); + } + template + void report_error(const char *format, const Args & ... args) { + vreport_error(format, fmt::make_format_args(args...)); + } + ``` + +- Added the `make_printf_args` function for capturing `printf` + arguments (https://github.com/fmtlib/fmt/issues/687, + https://github.com/fmtlib/fmt/pull/694). Thanks @Kronuz. + +- Added prefix `v` to non-variadic functions taking `format_args` to + distinguish them from variadic ones: + + ```c++ + std::string vformat(string_view format_str, format_args args); + + template + std::string format(string_view format_str, const Args & ... args); + ``` + +- Added experimental support for formatting ranges, containers and + tuple-like types in `fmt/ranges.h` + (https://github.com/fmtlib/fmt/pull/735): + + ```c++ + #include + + std::vector v = {1, 2, 3}; + fmt::print("{}", v); // prints {1, 2, 3} + ``` + + Thanks @Remotion. + +- Implemented `wchar_t` date and time formatting + (https://github.com/fmtlib/fmt/pull/712): + + ```c++ + #include + + std::time_t t = std::time(nullptr); + auto s = fmt::format(L"The date is {:%Y-%m-%d}.", *std::localtime(&t)); + ``` + + Thanks @DanielaE. + +- Provided more wide string overloads + (https://github.com/fmtlib/fmt/pull/724). Thanks @DanielaE. + +- Switched from a custom null-terminated string view class to + `string_view` in the format API and provided `fmt::string_view` + which implements a subset of `std::string_view` API for pre-C++17 + systems. + +- Added support for `std::experimental::string_view` + (https://github.com/fmtlib/fmt/pull/607): + + ```c++ + #include + #include + + fmt::print("{}", std::experimental::string_view("foo")); + ``` + + Thanks @virgiliofornazin. + +- Allowed mixing named and automatic arguments: + + ```c++ + fmt::format("{} {two}", 1, fmt::arg("two", 2)); + ``` + +- Removed the write API in favor of the [format + API](https://fmt.dev/latest/api.html#format-api) with compile-time + handling of format strings. + +- Disallowed formatting of multibyte strings into a wide character + target (https://github.com/fmtlib/fmt/pull/606). + +- Improved documentation + (https://github.com/fmtlib/fmt/pull/515, + https://github.com/fmtlib/fmt/issues/614, + https://github.com/fmtlib/fmt/pull/617, + https://github.com/fmtlib/fmt/pull/661, + https://github.com/fmtlib/fmt/pull/680). + Thanks @ibell, @mihaitodor and @johnthagen. + +- Implemented more efficient handling of large number of format + arguments. + +- Introduced an inline namespace for symbol versioning. + +- Added debug postfix `d` to the `fmt` library name + (https://github.com/fmtlib/fmt/issues/636). + +- Removed unnecessary `fmt/` prefix in includes + (https://github.com/fmtlib/fmt/pull/397). Thanks @chronoxor. + +- Moved `fmt/*.h` to `include/fmt/*.h` to prevent irrelevant files and + directories appearing on the include search paths when fmt is used + as a subproject and moved source files to the `src` directory. + +- Added qmake project file `support/fmt.pro` + (https://github.com/fmtlib/fmt/pull/641). Thanks @cowo78. + +- Added Gradle build file `support/build.gradle` + (https://github.com/fmtlib/fmt/pull/649). Thanks @luncliff. + +- Removed `FMT_CPPFORMAT` CMake option. + +- Fixed a name conflict with the macro `CHAR_WIDTH` in glibc + (https://github.com/fmtlib/fmt/pull/616). Thanks @aroig. + +- Fixed handling of nested braces in `fmt::join` + (https://github.com/fmtlib/fmt/issues/638). + +- Added `SOURCELINK_SUFFIX` for compatibility with Sphinx 1.5 + (https://github.com/fmtlib/fmt/pull/497). Thanks @ginggs. + +- Added a missing `inline` in the header-only mode + (https://github.com/fmtlib/fmt/pull/626). Thanks @aroig. + +- Fixed various compiler warnings + (https://github.com/fmtlib/fmt/pull/640, + https://github.com/fmtlib/fmt/pull/656, + https://github.com/fmtlib/fmt/pull/679, + https://github.com/fmtlib/fmt/pull/681, + https://github.com/fmtlib/fmt/pull/705, + https://github.com/fmtlib/fmt/issues/715, + https://github.com/fmtlib/fmt/pull/717, + https://github.com/fmtlib/fmt/pull/720, + https://github.com/fmtlib/fmt/pull/723, + https://github.com/fmtlib/fmt/pull/726, + https://github.com/fmtlib/fmt/pull/730, + https://github.com/fmtlib/fmt/pull/739). + Thanks @peterbell10, @LarsGullik, @foonathan, @eliaskosunen, + @christianparpart, @DanielaE and @mwinterb. + +- Worked around an MSVC bug and fixed several warnings + (https://github.com/fmtlib/fmt/pull/653). Thanks @alabuzhev. + +- Worked around GCC bug 67371 + (https://github.com/fmtlib/fmt/issues/682). + +- Fixed compilation with `-fno-exceptions` + (https://github.com/fmtlib/fmt/pull/655). Thanks @chenxiaolong. + +- Made `constexpr remove_prefix` gcc version check tighter + (https://github.com/fmtlib/fmt/issues/648). + +- Renamed internal type enum constants to prevent collision with + poorly written C libraries + (https://github.com/fmtlib/fmt/issues/644). + +- Added detection of `wostream operator<<` + (https://github.com/fmtlib/fmt/issues/650). + +- Fixed compilation on OpenBSD + (https://github.com/fmtlib/fmt/pull/660). Thanks @hubslave. + +- Fixed compilation on FreeBSD 12 + (https://github.com/fmtlib/fmt/pull/732). Thanks @dankm. + +- Fixed compilation when there is a mismatch between `-std` options + between the library and user code + (https://github.com/fmtlib/fmt/issues/664). + +- Fixed compilation with GCC 7 and `-std=c++11` + (https://github.com/fmtlib/fmt/issues/734). + +- Improved generated binary code on GCC 7 and older + (https://github.com/fmtlib/fmt/issues/668). + +- Fixed handling of numeric alignment with no width + (https://github.com/fmtlib/fmt/issues/675). + +- Fixed handling of empty strings in UTF8/16 converters + (https://github.com/fmtlib/fmt/pull/676). Thanks @vgalka-sl. + +- Fixed formatting of an empty `string_view` + (https://github.com/fmtlib/fmt/issues/689). + +- Fixed detection of `string_view` on libc++ + (https://github.com/fmtlib/fmt/issues/686). + +- Fixed DLL issues (https://github.com/fmtlib/fmt/pull/696). + Thanks @sebkoenig. + +- Fixed compile checks for mixing narrow and wide strings + (https://github.com/fmtlib/fmt/issues/690). + +- Disabled unsafe implicit conversion to `std::string` + (https://github.com/fmtlib/fmt/issues/729). + +- Fixed handling of reused format specs (as in `fmt::join`) for + pointers (https://github.com/fmtlib/fmt/pull/725). Thanks @mwinterb. + +- Fixed installation of `fmt/ranges.h` + (https://github.com/fmtlib/fmt/pull/738). Thanks @sv1990. + +# 4.1.0 - 2017-12-20 + +- Added `fmt::to_wstring()` in addition to `fmt::to_string()` + (https://github.com/fmtlib/fmt/pull/559). Thanks @alabuzhev. +- Added support for C++17 `std::string_view` + (https://github.com/fmtlib/fmt/pull/571 and + https://github.com/fmtlib/fmt/pull/578). + Thanks @thelostt and @mwinterb. +- Enabled stream exceptions to catch errors + (https://github.com/fmtlib/fmt/issues/581). Thanks @crusader-mike. +- Allowed formatting of class hierarchies with `fmt::format_arg()` + (https://github.com/fmtlib/fmt/pull/547). Thanks @rollbear. +- Removed limitations on character types + (https://github.com/fmtlib/fmt/pull/563). Thanks @Yelnats321. +- Conditionally enabled use of `std::allocator_traits` + (https://github.com/fmtlib/fmt/pull/583). Thanks @mwinterb. +- Added support for `const` variadic member function emulation with + `FMT_VARIADIC_CONST` + (https://github.com/fmtlib/fmt/pull/591). Thanks @ludekvodicka. +- Various bugfixes: bad overflow check, unsupported implicit type + conversion when determining formatting function, test segfaults + (https://github.com/fmtlib/fmt/issues/551), ill-formed + macros (https://github.com/fmtlib/fmt/pull/542) and + ambiguous overloads + (https://github.com/fmtlib/fmt/issues/580). Thanks @xylosper. +- Prevented warnings on MSVC + (https://github.com/fmtlib/fmt/pull/605, + https://github.com/fmtlib/fmt/pull/602, and + https://github.com/fmtlib/fmt/pull/545), clang + (https://github.com/fmtlib/fmt/pull/582), GCC + (https://github.com/fmtlib/fmt/issues/573), various + conversion warnings (https://github.com/fmtlib/fmt/pull/609, + https://github.com/fmtlib/fmt/pull/567, + https://github.com/fmtlib/fmt/pull/553 and + https://github.com/fmtlib/fmt/pull/553), and added + `override` and `[[noreturn]]` + (https://github.com/fmtlib/fmt/pull/549 and + https://github.com/fmtlib/fmt/issues/555). + Thanks @alabuzhev, @virgiliofornazin, @alexanderbock, @yumetodo, @VaderY, + @jpcima, @thelostt and @Manu343726. +- Improved CMake: Used `GNUInstallDirs` to set installation location + (https://github.com/fmtlib/fmt/pull/610) and fixed warnings + (https://github.com/fmtlib/fmt/pull/536 and + https://github.com/fmtlib/fmt/pull/556). + Thanks @mikecrowe, @evgen231 and @henryiii. + +# 4.0.0 - 2017-06-27 + +- Removed old compatibility headers `cppformat/*.h` and CMake options + (https://github.com/fmtlib/fmt/pull/527). Thanks @maddinat0r. + +- Added `string.h` containing `fmt::to_string()` as alternative to + `std::to_string()` as well as other string writer functionality + (https://github.com/fmtlib/fmt/issues/326 and + https://github.com/fmtlib/fmt/pull/441): + + ```c++ + #include "fmt/string.h" + + std::string answer = fmt::to_string(42); + ``` + + Thanks @glebov-andrey. + +- Moved `fmt::printf()` to new `printf.h` header and allowed `%s` as + generic specifier (https://github.com/fmtlib/fmt/pull/453), + made `%.f` more conformant to regular `printf()` + (https://github.com/fmtlib/fmt/pull/490), added custom + writer support (https://github.com/fmtlib/fmt/issues/476) + and implemented missing custom argument formatting + (https://github.com/fmtlib/fmt/pull/339 and + https://github.com/fmtlib/fmt/pull/340): + + ```c++ + #include "fmt/printf.h" + + // %s format specifier can be used with any argument type. + fmt::printf("%s", 42); + ``` + + Thanks @mojoBrendan, @manylegged and @spacemoose. + See also https://github.com/fmtlib/fmt/issues/360, + https://github.com/fmtlib/fmt/issues/335 and + https://github.com/fmtlib/fmt/issues/331. + +- Added `container.h` containing a `BasicContainerWriter` to write to + containers like `std::vector` + (https://github.com/fmtlib/fmt/pull/450). Thanks @polyvertex. + +- Added `fmt::join()` function that takes a range and formats its + elements separated by a given string + (https://github.com/fmtlib/fmt/pull/466): + + ```c++ + #include "fmt/format.h" + + std::vector v = {1.2, 3.4, 5.6}; + // Prints "(+01.20, +03.40, +05.60)". + fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", ")); + ``` + + Thanks @olivier80. + +- Added support for custom formatting specifications to simplify + customization of built-in formatting + (https://github.com/fmtlib/fmt/pull/444). Thanks @polyvertex. + See also https://github.com/fmtlib/fmt/issues/439. + +- Added `fmt::format_system_error()` for error code formatting + (https://github.com/fmtlib/fmt/issues/323 and + https://github.com/fmtlib/fmt/pull/526). Thanks @maddinat0r. + +- Added thread-safe `fmt::localtime()` and `fmt::gmtime()` as + replacement for the standard version to `time.h` + (https://github.com/fmtlib/fmt/pull/396). Thanks @codicodi. + +- Internal improvements to `NamedArg` and `ArgLists` + (https://github.com/fmtlib/fmt/pull/389 and + https://github.com/fmtlib/fmt/pull/390). Thanks @chronoxor. + +- Fixed crash due to bug in `FormatBuf` + (https://github.com/fmtlib/fmt/pull/493). Thanks @effzeh. See also + https://github.com/fmtlib/fmt/issues/480 and + https://github.com/fmtlib/fmt/issues/491. + +- Fixed handling of wide strings in `fmt::StringWriter`. + +- Improved compiler error messages + (https://github.com/fmtlib/fmt/issues/357). + +- Fixed various warnings and issues with various compilers + (https://github.com/fmtlib/fmt/pull/494, + https://github.com/fmtlib/fmt/pull/499, + https://github.com/fmtlib/fmt/pull/483, + https://github.com/fmtlib/fmt/pull/485, + https://github.com/fmtlib/fmt/pull/482, + https://github.com/fmtlib/fmt/pull/475, + https://github.com/fmtlib/fmt/pull/473 and + https://github.com/fmtlib/fmt/pull/414). + Thanks @chronoxor, @zhaohuaxishi, @pkestene, @dschmidt and @0x414c. + +- Improved CMake: targets are now namespaced + (https://github.com/fmtlib/fmt/pull/511 and + https://github.com/fmtlib/fmt/pull/513), supported + header-only `printf.h` + (https://github.com/fmtlib/fmt/pull/354), fixed issue with + minimal supported library subset + (https://github.com/fmtlib/fmt/issues/418, + https://github.com/fmtlib/fmt/pull/419 and + https://github.com/fmtlib/fmt/pull/420). + Thanks @bjoernthiel, @niosHD, @LogicalKnight and @alabuzhev. + +- Improved documentation (https://github.com/fmtlib/fmt/pull/393). + Thanks @pwm1234. + +# 3.0.2 - 2017-06-14 + +- Added `FMT_VERSION` macro + (https://github.com/fmtlib/fmt/issues/411). +- Used `FMT_NULL` instead of literal `0` + (https://github.com/fmtlib/fmt/pull/409). Thanks @alabuzhev. +- Added extern templates for `format_float` + (https://github.com/fmtlib/fmt/issues/413). +- Fixed implicit conversion issue + (https://github.com/fmtlib/fmt/issues/507). +- Fixed signbit detection + (https://github.com/fmtlib/fmt/issues/423). +- Fixed naming collision + (https://github.com/fmtlib/fmt/issues/425). +- Fixed missing intrinsic for C++/CLI + (https://github.com/fmtlib/fmt/pull/457). Thanks @calumr. +- Fixed Android detection + (https://github.com/fmtlib/fmt/pull/458). Thanks @Gachapen. +- Use lean `windows.h` if not in header-only mode + (https://github.com/fmtlib/fmt/pull/503). Thanks @Quentin01. +- Fixed issue with CMake exporting C++11 flag + (https://github.com/fmtlib/fmt/pull/455). Thanks @EricWF. +- Fixed issue with nvcc and MSVC compiler bug and MinGW + (https://github.com/fmtlib/fmt/issues/505). +- Fixed DLL issues (https://github.com/fmtlib/fmt/pull/469 and + https://github.com/fmtlib/fmt/pull/502). + Thanks @richardeakin and @AndreasSchoenle. +- Fixed test compilation under FreeBSD + (https://github.com/fmtlib/fmt/issues/433). +- Fixed various warnings + (https://github.com/fmtlib/fmt/pull/403, + https://github.com/fmtlib/fmt/pull/410 and + https://github.com/fmtlib/fmt/pull/510). + Thanks @Lecetem, @chenhayat and @trozen. +- Worked around a broken `__builtin_clz` in clang with MS codegen + (https://github.com/fmtlib/fmt/issues/519). +- Removed redundant include + (https://github.com/fmtlib/fmt/issues/479). +- Fixed documentation issues. + +# 3.0.1 - 2016-11-01 + +- Fixed handling of thousands separator + (https://github.com/fmtlib/fmt/issues/353). +- Fixed handling of `unsigned char` strings + (https://github.com/fmtlib/fmt/issues/373). +- Corrected buffer growth when formatting time + (https://github.com/fmtlib/fmt/issues/367). +- Removed warnings under MSVC and clang + (https://github.com/fmtlib/fmt/issues/318, + https://github.com/fmtlib/fmt/issues/250, also merged + https://github.com/fmtlib/fmt/pull/385 and + https://github.com/fmtlib/fmt/pull/361). + Thanks @jcelerier and @nmoehrle. +- Fixed compilation issues under Android + (https://github.com/fmtlib/fmt/pull/327, + https://github.com/fmtlib/fmt/issues/345 and + https://github.com/fmtlib/fmt/pull/381), FreeBSD + (https://github.com/fmtlib/fmt/pull/358), Cygwin + (https://github.com/fmtlib/fmt/issues/388), MinGW + (https://github.com/fmtlib/fmt/issues/355) as well as other + issues (https://github.com/fmtlib/fmt/issues/350, + https://github.com/fmtlib/fmt/issues/355, + https://github.com/fmtlib/fmt/pull/348, + https://github.com/fmtlib/fmt/pull/402, + https://github.com/fmtlib/fmt/pull/405). + Thanks @dpantele, @hghwng, @arvedarved, @LogicalKnight and @JanHellwig. +- Fixed some documentation issues and extended specification + (https://github.com/fmtlib/fmt/issues/320, + https://github.com/fmtlib/fmt/pull/333, + https://github.com/fmtlib/fmt/issues/347, + https://github.com/fmtlib/fmt/pull/362). Thanks @smellman. + +# 3.0.0 - 2016-05-07 + +- The project has been renamed from C++ Format (cppformat) to fmt for + consistency with the used namespace and macro prefix + (https://github.com/fmtlib/fmt/issues/307). Library headers + are now located in the `fmt` directory: + + ```c++ + #include "fmt/format.h" + ``` + + Including `format.h` from the `cppformat` directory is deprecated + but works via a proxy header which will be removed in the next major + version. + + The documentation is now available at . + +- Added support for + [strftime](http://en.cppreference.com/w/cpp/chrono/c/strftime)-like + [date and time + formatting](https://fmt.dev/3.0.0/api.html#date-and-time-formatting) + (https://github.com/fmtlib/fmt/issues/283): + + ```c++ + #include "fmt/time.h" + + std::time_t t = std::time(nullptr); + // Prints "The date is 2016-04-29." (with the current date) + fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t)); + ``` + +- `std::ostream` support including formatting of user-defined types + that provide overloaded `operator<<` has been moved to + `fmt/ostream.h`: + + ```c++ + #include "fmt/ostream.h" + + class Date { + int year_, month_, day_; + public: + Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} + + friend std::ostream &operator<<(std::ostream &os, const Date &d) { + return os << d.year_ << '-' << d.month_ << '-' << d.day_; + } + }; + + std::string s = fmt::format("The date is {}", Date(2012, 12, 9)); + // s == "The date is 2012-12-9" + ``` + +- Added support for [custom argument + formatters](https://fmt.dev/3.0.0/api.html#argument-formatters) + (https://github.com/fmtlib/fmt/issues/235). + +- Added support for locale-specific integer formatting with the `n` + specifier (https://github.com/fmtlib/fmt/issues/305): + + ```c++ + std::setlocale(LC_ALL, "en_US.utf8"); + fmt::print("cppformat: {:n}\n", 1234567); // prints 1,234,567 + ``` + +- Sign is now preserved when formatting an integer with an incorrect + `printf` format specifier + (https://github.com/fmtlib/fmt/issues/265): + + ```c++ + fmt::printf("%lld", -42); // prints -42 + ``` + + Note that it would be an undefined behavior in `std::printf`. + +- Length modifiers such as `ll` are now optional in printf formatting + functions and the correct type is determined automatically + (https://github.com/fmtlib/fmt/issues/255): + + ```c++ + fmt::printf("%d", std::numeric_limits::max()); + ``` + + Note that it would be an undefined behavior in `std::printf`. + +- Added initial support for custom formatters + (https://github.com/fmtlib/fmt/issues/231). + +- Fixed detection of user-defined literal support on Intel C++ + compiler (https://github.com/fmtlib/fmt/issues/311, + https://github.com/fmtlib/fmt/pull/312). + Thanks @dean0x7d and @speth. + +- Reduced compile time + (https://github.com/fmtlib/fmt/pull/243, + https://github.com/fmtlib/fmt/pull/249, + https://github.com/fmtlib/fmt/issues/317): + + ![](https://cloud.githubusercontent.com/assets/4831417/11614060/b9e826d2-9c36-11e5-8666-d4131bf503ef.png) + + ![](https://cloud.githubusercontent.com/assets/4831417/11614080/6ac903cc-9c37-11e5-8165-26df6efae364.png) + + Thanks @dean0x7d. + +- Compile test fixes (https://github.com/fmtlib/fmt/pull/313). + Thanks @dean0x7d. + +- Documentation fixes (https://github.com/fmtlib/fmt/pull/239, + https://github.com/fmtlib/fmt/issues/248, + https://github.com/fmtlib/fmt/issues/252, + https://github.com/fmtlib/fmt/pull/258, + https://github.com/fmtlib/fmt/issues/260, + https://github.com/fmtlib/fmt/issues/301, + https://github.com/fmtlib/fmt/pull/309). + Thanks @ReadmeCritic @Gachapen and @jwilk. + +- Fixed compiler and sanitizer warnings + (https://github.com/fmtlib/fmt/issues/244, + https://github.com/fmtlib/fmt/pull/256, + https://github.com/fmtlib/fmt/pull/259, + https://github.com/fmtlib/fmt/issues/263, + https://github.com/fmtlib/fmt/issues/274, + https://github.com/fmtlib/fmt/pull/277, + https://github.com/fmtlib/fmt/pull/286, + https://github.com/fmtlib/fmt/issues/291, + https://github.com/fmtlib/fmt/issues/296, + https://github.com/fmtlib/fmt/issues/308). + Thanks @mwinterb, @pweiskircher and @Naios. + +- Improved compatibility with Windows Store apps + (https://github.com/fmtlib/fmt/issues/280, + https://github.com/fmtlib/fmt/pull/285) Thanks @mwinterb. + +- Added tests of compatibility with older C++ standards + (https://github.com/fmtlib/fmt/pull/273). Thanks @niosHD. + +- Fixed Android build + (https://github.com/fmtlib/fmt/pull/271). Thanks @newnon. + +- Changed `ArgMap` to be backed by a vector instead of a map. + (https://github.com/fmtlib/fmt/issues/261, + https://github.com/fmtlib/fmt/pull/262). Thanks @mwinterb. + +- Added `fprintf` overload that writes to a `std::ostream` + (https://github.com/fmtlib/fmt/pull/251). + Thanks @nickhutchinson. + +- Export symbols when building a Windows DLL + (https://github.com/fmtlib/fmt/pull/245). + Thanks @macdems. + +- Fixed compilation on Cygwin + (https://github.com/fmtlib/fmt/issues/304). + +- Implemented a workaround for a bug in Apple LLVM version 4.2 of + clang (https://github.com/fmtlib/fmt/issues/276). + +- Implemented a workaround for Google Test bug + https://github.com/google/googletest/issues/705 on gcc 6 + (https://github.com/fmtlib/fmt/issues/268). Thanks @octoploid. + +- Removed Biicode support because the latter has been discontinued. + +# 2.1.1 - 2016-04-11 + +- The install location for generated CMake files is now configurable + via the `FMT_CMAKE_DIR` CMake variable + (https://github.com/fmtlib/fmt/pull/299). Thanks @niosHD. +- Documentation fixes + (https://github.com/fmtlib/fmt/issues/252). + +# 2.1.0 - 2016-03-21 + +- Project layout and build system improvements + (https://github.com/fmtlib/fmt/pull/267): + + - The code have been moved to the `cppformat` directory. Including + `format.h` from the top-level directory is deprecated but works + via a proxy header which will be removed in the next major + version. + - C++ Format CMake targets now have proper interface definitions. + - Installed version of the library now supports the header-only + configuration. + - Targets `doc`, `install`, and `test` are now disabled if C++ + Format is included as a CMake subproject. They can be enabled by + setting `FMT_DOC`, `FMT_INSTALL`, and `FMT_TEST` in the parent + project. + + Thanks @niosHD. + +# 2.0.1 - 2016-03-13 + +- Improved CMake find and package support + (https://github.com/fmtlib/fmt/issues/264). Thanks @niosHD. +- Fix compile error with Android NDK and mingw32 + (https://github.com/fmtlib/fmt/issues/241). Thanks @Gachapen. +- Documentation fixes + (https://github.com/fmtlib/fmt/issues/248, + https://github.com/fmtlib/fmt/issues/260). + +# 2.0.0 - 2015-12-01 + +## General + +- \[Breaking\] Named arguments + (https://github.com/fmtlib/fmt/pull/169, + https://github.com/fmtlib/fmt/pull/173, + https://github.com/fmtlib/fmt/pull/174): + + ```c++ + fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + ``` + + Thanks @jamboree. + +- \[Experimental\] User-defined literals for format and named + arguments (https://github.com/fmtlib/fmt/pull/204, + https://github.com/fmtlib/fmt/pull/206, + https://github.com/fmtlib/fmt/pull/207): + + ```c++ + using namespace fmt::literals; + fmt::print("The answer is {answer}.", "answer"_a=42); + ``` + + Thanks @dean0x7d. + +- \[Breaking\] Formatting of more than 16 arguments is now supported + when using variadic templates + (https://github.com/fmtlib/fmt/issues/141). Thanks @Shauren. + +- Runtime width specification + (https://github.com/fmtlib/fmt/pull/168): + + ```c++ + fmt::format("{0:{1}}", 42, 5); // gives " 42" + ``` + + Thanks @jamboree. + +- \[Breaking\] Enums are now formatted with an overloaded + `std::ostream` insertion operator (`operator<<`) if available + (https://github.com/fmtlib/fmt/issues/232). + +- \[Breaking\] Changed default `bool` format to textual, \"true\" or + \"false\" (https://github.com/fmtlib/fmt/issues/170): + + ```c++ + fmt::print("{}", true); // prints "true" + ``` + + To print `bool` as a number use numeric format specifier such as + `d`: + + ```c++ + fmt::print("{:d}", true); // prints "1" + ``` + +- `fmt::printf` and `fmt::sprintf` now support formatting of `bool` + with the `%s` specifier giving textual output, \"true\" or \"false\" + (https://github.com/fmtlib/fmt/pull/223): + + ```c++ + fmt::printf("%s", true); // prints "true" + ``` + + Thanks @LarsGullik. + +- \[Breaking\] `signed char` and `unsigned char` are now formatted as + integers by default + (https://github.com/fmtlib/fmt/pull/217). + +- \[Breaking\] Pointers to C strings can now be formatted with the `p` + specifier (https://github.com/fmtlib/fmt/pull/223): + + ```c++ + fmt::print("{:p}", "test"); // prints pointer value + ``` + + Thanks @LarsGullik. + +- \[Breaking\] `fmt::printf` and `fmt::sprintf` now print null + pointers as `(nil)` and null strings as `(null)` for consistency + with glibc (https://github.com/fmtlib/fmt/pull/226). + Thanks @LarsGullik. + +- \[Breaking\] `fmt::(s)printf` now supports formatting of objects of + user-defined types that provide an overloaded `std::ostream` + insertion operator (`operator<<`) + (https://github.com/fmtlib/fmt/issues/201): + + ```c++ + fmt::printf("The date is %s", Date(2012, 12, 9)); + ``` + +- \[Breaking\] The `Buffer` template is now part of the public API and + can be used to implement custom memory buffers + (https://github.com/fmtlib/fmt/issues/140). Thanks @polyvertex. + +- \[Breaking\] Improved compatibility between `BasicStringRef` and + [std::experimental::basic_string_view]( + http://en.cppreference.com/w/cpp/experimental/basic_string_view) + (https://github.com/fmtlib/fmt/issues/100, + https://github.com/fmtlib/fmt/issues/159, + https://github.com/fmtlib/fmt/issues/183): + + - Comparison operators now compare string content, not pointers + - `BasicStringRef::c_str` replaced by `BasicStringRef::data` + - `BasicStringRef` is no longer assumed to be null-terminated + + References to null-terminated strings are now represented by a new + class, `BasicCStringRef`. + +- Dependency on pthreads introduced by Google Test is now optional + (https://github.com/fmtlib/fmt/issues/185). + +- New CMake options `FMT_DOC`, `FMT_INSTALL` and `FMT_TEST` to control + generation of `doc`, `install` and `test` targets respectively, on + by default (https://github.com/fmtlib/fmt/issues/197, + https://github.com/fmtlib/fmt/issues/198, + https://github.com/fmtlib/fmt/issues/200). Thanks @maddinat0r. + +- `noexcept` is now used when compiling with MSVC2015 + (https://github.com/fmtlib/fmt/pull/215). Thanks @dmkrepo. + +- Added an option to disable use of `windows.h` when + `FMT_USE_WINDOWS_H` is defined as 0 before including `format.h` + (https://github.com/fmtlib/fmt/issues/171). Thanks @alfps. + +- \[Breaking\] `windows.h` is now included with `NOMINMAX` unless + `FMT_WIN_MINMAX` is defined. This is done to prevent breaking code + using `std::min` and `std::max` and only affects the header-only + configuration (https://github.com/fmtlib/fmt/issues/152, + https://github.com/fmtlib/fmt/pull/153, + https://github.com/fmtlib/fmt/pull/154). Thanks @DevO2012. + +- Improved support for custom character types + (https://github.com/fmtlib/fmt/issues/171). Thanks @alfps. + +- Added an option to disable use of IOStreams when `FMT_USE_IOSTREAMS` + is defined as 0 before including `format.h` + (https://github.com/fmtlib/fmt/issues/205, + https://github.com/fmtlib/fmt/pull/208). Thanks @JodiTheTigger. + +- Improved detection of `isnan`, `isinf` and `signbit`. + +## Optimization + +- Made formatting of user-defined types more efficient with a custom + stream buffer (https://github.com/fmtlib/fmt/issues/92, + https://github.com/fmtlib/fmt/pull/230). Thanks @NotImplemented. +- Further improved performance of `fmt::Writer` on integer formatting + and fixed a minor regression. Now it is \~7% faster than + `karma::generate` on Karma\'s benchmark + (https://github.com/fmtlib/fmt/issues/186). +- \[Breaking\] Reduced [compiled code + size](https://github.com/fmtlib/fmt#compile-time-and-code-bloat) + (https://github.com/fmtlib/fmt/issues/143, + https://github.com/fmtlib/fmt/pull/149). + +## Distribution + +- \[Breaking\] Headers are now installed in + `${CMAKE_INSTALL_PREFIX}/include/cppformat` + (https://github.com/fmtlib/fmt/issues/178). Thanks @jackyf. + +- \[Breaking\] Changed the library name from `format` to `cppformat` + for consistency with the project name and to avoid potential + conflicts (https://github.com/fmtlib/fmt/issues/178). + Thanks @jackyf. + +- C++ Format is now available in [Debian](https://www.debian.org/) + GNU/Linux + ([stretch](https://packages.debian.org/source/stretch/cppformat), + [sid](https://packages.debian.org/source/sid/cppformat)) and derived + distributions such as + [Ubuntu](https://launchpad.net/ubuntu/+source/cppformat) 15.10 and + later (https://github.com/fmtlib/fmt/issues/155): + + $ sudo apt-get install libcppformat1-dev + + Thanks @jackyf. + +- [Packages for Fedora and + RHEL](https://admin.fedoraproject.org/pkgdb/package/cppformat/) are + now available. Thanks Dave Johansen. + +- C++ Format can now be installed via [Homebrew](http://brew.sh/) on + OS X (https://github.com/fmtlib/fmt/issues/157): + + $ brew install cppformat + + Thanks @ortho and Anatoliy Bulukin. + +## Documentation + +- Migrated from ReadTheDocs to GitHub Pages for better responsiveness + and reliability (https://github.com/fmtlib/fmt/issues/128). + New documentation address is . +- Added [Building thedocumentation]( + https://fmt.dev/2.0.0/usage.html#building-the-documentation) + section to the documentation. +- Documentation build script is now compatible with Python 3 and newer + pip versions. (https://github.com/fmtlib/fmt/pull/189, + https://github.com/fmtlib/fmt/issues/209). + Thanks @JodiTheTigger and @xentec. +- Documentation fixes and improvements + (https://github.com/fmtlib/fmt/issues/36, + https://github.com/fmtlib/fmt/issues/75, + https://github.com/fmtlib/fmt/issues/125, + https://github.com/fmtlib/fmt/pull/160, + https://github.com/fmtlib/fmt/pull/161, + https://github.com/fmtlib/fmt/issues/162, + https://github.com/fmtlib/fmt/issues/165, + https://github.com/fmtlib/fmt/issues/210). + Thanks @syohex. +- Fixed out-of-tree documentation build + (https://github.com/fmtlib/fmt/issues/177). Thanks @jackyf. + +## Fixes + +- Fixed `initializer_list` detection + (https://github.com/fmtlib/fmt/issues/136). Thanks @Gachapen. + +- \[Breaking\] Fixed formatting of enums with numeric format + specifiers in `fmt::(s)printf` + (https://github.com/fmtlib/fmt/issues/131, + https://github.com/fmtlib/fmt/issues/139): + + ```c++ + enum { ANSWER = 42 }; + fmt::printf("%d", ANSWER); + ``` + + Thanks @Naios. + +- Improved compatibility with old versions of MinGW + (https://github.com/fmtlib/fmt/issues/129, + https://github.com/fmtlib/fmt/pull/130, + https://github.com/fmtlib/fmt/issues/132). Thanks @cstamford. + +- Fixed a compile error on MSVC with disabled exceptions + (https://github.com/fmtlib/fmt/issues/144). + +- Added a workaround for broken implementation of variadic templates + in MSVC2012 (https://github.com/fmtlib/fmt/issues/148). + +- Placed the anonymous namespace within `fmt` namespace for the + header-only configuration (https://github.com/fmtlib/fmt/issues/171). + Thanks @alfps. + +- Fixed issues reported by Coverity Scan + (https://github.com/fmtlib/fmt/issues/187, + https://github.com/fmtlib/fmt/issues/192). + +- Implemented a workaround for a name lookup bug in MSVC2010 + (https://github.com/fmtlib/fmt/issues/188). + +- Fixed compiler warnings + (https://github.com/fmtlib/fmt/issues/95, + https://github.com/fmtlib/fmt/issues/96, + https://github.com/fmtlib/fmt/pull/114, + https://github.com/fmtlib/fmt/issues/135, + https://github.com/fmtlib/fmt/issues/142, + https://github.com/fmtlib/fmt/issues/145, + https://github.com/fmtlib/fmt/issues/146, + https://github.com/fmtlib/fmt/issues/158, + https://github.com/fmtlib/fmt/issues/163, + https://github.com/fmtlib/fmt/issues/175, + https://github.com/fmtlib/fmt/issues/190, + https://github.com/fmtlib/fmt/pull/191, + https://github.com/fmtlib/fmt/issues/194, + https://github.com/fmtlib/fmt/pull/196, + https://github.com/fmtlib/fmt/issues/216, + https://github.com/fmtlib/fmt/pull/218, + https://github.com/fmtlib/fmt/pull/220, + https://github.com/fmtlib/fmt/pull/229, + https://github.com/fmtlib/fmt/issues/233, + https://github.com/fmtlib/fmt/issues/234, + https://github.com/fmtlib/fmt/pull/236, + https://github.com/fmtlib/fmt/issues/281, + https://github.com/fmtlib/fmt/issues/289). + Thanks @seanmiddleditch, @dixlorenz, @CarterLi, @Naios, @fmatthew5876, + @LevskiWeng, @rpopescu, @gabime, @cubicool, @jkflying, @LogicalKnight, + @inguin and @Jopie64. + +- Fixed portability issues (mostly causing test failures) on ARM, + ppc64, ppc64le, s390x and SunOS 5.11 i386 + (https://github.com/fmtlib/fmt/issues/138, + https://github.com/fmtlib/fmt/issues/179, + https://github.com/fmtlib/fmt/issues/180, + https://github.com/fmtlib/fmt/issues/202, + https://github.com/fmtlib/fmt/issues/225, [Red Hat Bugzilla + Bug 1260297](https://bugzilla.redhat.com/show_bug.cgi?id=1260297)). + Thanks @Naios, @jackyf and Dave Johansen. + +- Fixed a name conflict with macro `free` defined in `crtdbg.h` when + `_CRTDBG_MAP_ALLOC` is set (https://github.com/fmtlib/fmt/issues/211). + +- Fixed shared library build on OS X + (https://github.com/fmtlib/fmt/pull/212). Thanks @dean0x7d. + +- Fixed an overload conflict on MSVC when `/Zc:wchar_t-` option is + specified (https://github.com/fmtlib/fmt/pull/214). + Thanks @slavanap. + +- Improved compatibility with MSVC 2008 + (https://github.com/fmtlib/fmt/pull/236). Thanks @Jopie64. + +- Improved compatibility with bcc32 + (https://github.com/fmtlib/fmt/issues/227). + +- Fixed `static_assert` detection on Clang + (https://github.com/fmtlib/fmt/pull/228). Thanks @dean0x7d. + +# 1.1.0 - 2015-03-06 + +- Added `BasicArrayWriter`, a class template that provides operations + for formatting and writing data into a fixed-size array + (https://github.com/fmtlib/fmt/issues/105 and + https://github.com/fmtlib/fmt/issues/122): + + ```c++ + char buffer[100]; + fmt::ArrayWriter w(buffer); + w.write("The answer is {}", 42); + ``` + +- Added [0 A.D.](http://play0ad.com/) and [PenUltima Online + (POL)](http://www.polserver.com/) to the list of notable projects + using C++ Format. + +- C++ Format now uses MSVC intrinsics for better formatting performance + (https://github.com/fmtlib/fmt/pull/115, + https://github.com/fmtlib/fmt/pull/116, + https://github.com/fmtlib/fmt/pull/118 and + https://github.com/fmtlib/fmt/pull/121). Previously these + optimizations where only used on GCC and Clang. + Thanks @CarterLi and @objectx. + +- CMake install target + (https://github.com/fmtlib/fmt/pull/119). Thanks @TrentHouliston. + + You can now install C++ Format with `make install` command. + +- Improved [Biicode](http://www.biicode.com/) support + (https://github.com/fmtlib/fmt/pull/98 and + https://github.com/fmtlib/fmt/pull/104). + Thanks @MariadeAnton and @franramirez688. + +- Improved support for building with [Android NDK]( + https://developer.android.com/tools/sdk/ndk/index.html) + (https://github.com/fmtlib/fmt/pull/107). Thanks @newnon. + + The [android-ndk-example](https://github.com/fmtlib/android-ndk-example) + repository provides and example of using C++ Format with Android NDK: + + ![](https://raw.githubusercontent.com/fmtlib/android-ndk-example/master/screenshot.png) + +- Improved documentation of `SystemError` and `WindowsError` + (https://github.com/fmtlib/fmt/issues/54). + +- Various code improvements + (https://github.com/fmtlib/fmt/pull/110, + https://github.com/fmtlib/fmt/pull/111 + https://github.com/fmtlib/fmt/pull/112). Thanks @CarterLi. + +- Improved compile-time errors when formatting wide into narrow + strings (https://github.com/fmtlib/fmt/issues/117). + +- Fixed `BasicWriter::write` without formatting arguments when C++11 + support is disabled + (https://github.com/fmtlib/fmt/issues/109). + +- Fixed header-only build on OS X with GCC 4.9 + (https://github.com/fmtlib/fmt/issues/124). + +- Fixed packaging issues (https://github.com/fmtlib/fmt/issues/94). + +- Added [changelog](https://github.com/fmtlib/fmt/blob/master/ChangeLog.md) + (https://github.com/fmtlib/fmt/issues/103). + +# 1.0.0 - 2015-02-05 + +- Add support for a header-only configuration when `FMT_HEADER_ONLY` + is defined before including `format.h`: + + ```c++ + #define FMT_HEADER_ONLY + #include "format.h" + ``` + +- Compute string length in the constructor of `BasicStringRef` instead + of the `size` method + (https://github.com/fmtlib/fmt/issues/79). This eliminates + size computation for string literals on reasonable optimizing + compilers. + +- Fix formatting of types with overloaded `operator <<` for + `std::wostream` (https://github.com/fmtlib/fmt/issues/86): + + ```c++ + fmt::format(L"The date is {0}", Date(2012, 12, 9)); + ``` + +- Fix linkage of tests on Arch Linux + (https://github.com/fmtlib/fmt/issues/89). + +- Allow precision specifier for non-float arguments + (https://github.com/fmtlib/fmt/issues/90): + + ```c++ + fmt::print("{:.3}\n", "Carpet"); // prints "Car" + ``` + +- Fix build on Android NDK (https://github.com/fmtlib/fmt/issues/93). + +- Improvements to documentation build procedure. + +- Remove `FMT_SHARED` CMake variable in favor of standard [BUILD_SHARED_LIBS]( + http://www.cmake.org/cmake/help/v3.0/variable/BUILD_SHARED_LIBS.html). + +- Fix error handling in `fmt::fprintf`. + +- Fix a number of warnings. + +# 0.12.0 - 2014-10-25 + +- \[Breaking\] Improved separation between formatting and buffer + management. `Writer` is now a base class that cannot be instantiated + directly. The new `MemoryWriter` class implements the default buffer + management with small allocations done on stack. So `fmt::Writer` + should be replaced with `fmt::MemoryWriter` in variable + declarations. + + Old code: + + ```c++ + fmt::Writer w; + ``` + + New code: + + ```c++ + fmt::MemoryWriter w; + ``` + + If you pass `fmt::Writer` by reference, you can continue to do so: + + ```c++ + void f(fmt::Writer &w); + ``` + + This doesn\'t affect the formatting API. + +- Support for custom memory allocators + (https://github.com/fmtlib/fmt/issues/69) + +- Formatting functions now accept [signed char]{.title-ref} and + [unsigned char]{.title-ref} strings as arguments + (https://github.com/fmtlib/fmt/issues/73): + + ```c++ + auto s = format("GLSL version: {}", glGetString(GL_VERSION)); + ``` + +- Reduced code bloat. According to the new [benchmark + results](https://github.com/fmtlib/fmt#compile-time-and-code-bloat), + cppformat is close to `printf` and by the order of magnitude better + than Boost Format in terms of compiled code size. + +- Improved appearance of the documentation on mobile by using the + [Sphinx Bootstrap + theme](http://ryan-roemer.github.io/sphinx-bootstrap-theme/): + + | Old | New | + | --- | --- | + | ![](https://cloud.githubusercontent.com/assets/576385/4792130/cd256436-5de3-11e4-9a62-c077d0c2b003.png) | ![](https://cloud.githubusercontent.com/assets/576385/4792131/cd29896c-5de3-11e4-8f59-cac952942bf0.png) | + +# 0.11.0 - 2014-08-21 + +- Safe printf implementation with a POSIX extension for positional + arguments: + + ```c++ + fmt::printf("Elapsed time: %.2f seconds", 1.23); + fmt::printf("%1$s, %3$d %2$s", weekday, month, day); + ``` + +- Arguments of `char` type can now be formatted as integers (Issue + https://github.com/fmtlib/fmt/issues/55): + + ```c++ + fmt::format("0x{0:02X}", 'a'); + ``` + +- Deprecated parts of the API removed. + +- The library is now built and tested on MinGW with Appveyor in + addition to existing test platforms Linux/GCC, OS X/Clang, + Windows/MSVC. + +# 0.10.0 - 2014-07-01 + +**Improved API** + +- All formatting methods are now implemented as variadic functions + instead of using `operator<<` for feeding arbitrary arguments into a + temporary formatter object. This works both with C++11 where + variadic templates are used and with older standards where variadic + functions are emulated by providing lightweight wrapper functions + defined with the `FMT_VARIADIC` macro. You can use this macro for + defining your own portable variadic functions: + + ```c++ + void report_error(const char *format, const fmt::ArgList &args) { + fmt::print("Error: {}"); + fmt::print(format, args); + } + FMT_VARIADIC(void, report_error, const char *) + + report_error("file not found: {}", path); + ``` + + Apart from a more natural syntax, this also improves performance as + there is no need to construct temporary formatter objects and + control arguments\' lifetimes. Because the wrapper functions are + very lightweight, this doesn\'t cause code bloat even in pre-C++11 + mode. + +- Simplified common case of formatting an `std::string`. Now it + requires a single function call: + + ```c++ + std::string s = format("The answer is {}.", 42); + ``` + + Previously it required 2 function calls: + + ```c++ + std::string s = str(Format("The answer is {}.") << 42); + ``` + + Instead of unsafe `c_str` function, `fmt::Writer` should be used + directly to bypass creation of `std::string`: + + ```c++ + fmt::Writer w; + w.write("The answer is {}.", 42); + w.c_str(); // returns a C string + ``` + + This doesn\'t do dynamic memory allocation for small strings and is + less error prone as the lifetime of the string is the same as for + `std::string::c_str` which is well understood (hopefully). + +- Improved consistency in naming functions that are a part of the + public API. Now all public functions are lowercase following the + standard library conventions. Previously it was a combination of + lowercase and CapitalizedWords. Issue + https://github.com/fmtlib/fmt/issues/50. + +- Old functions are marked as deprecated and will be removed in the + next release. + +**Other Changes** + +- Experimental support for printf format specifications (work in + progress): + + ```c++ + fmt::printf("The answer is %d.", 42); + std::string s = fmt::sprintf("Look, a %s!", "string"); + ``` + +- Support for hexadecimal floating point format specifiers `a` and + `A`: + + ```c++ + print("{:a}", -42.0); // Prints -0x1.5p+5 + print("{:A}", -42.0); // Prints -0X1.5P+5 + ``` + +- CMake option `FMT_SHARED` that specifies whether to build format as + a shared library (off by default). + +# 0.9.0 - 2014-05-13 + +- More efficient implementation of variadic formatting functions. + +- `Writer::Format` now has a variadic overload: + + ```c++ + Writer out; + out.Format("Look, I'm {}!", "variadic"); + ``` + +- For efficiency and consistency with other overloads, variadic + overload of the `Format` function now returns `Writer` instead of + `std::string`. Use the `str` function to convert it to + `std::string`: + + ```c++ + std::string s = str(Format("Look, I'm {}!", "variadic")); + ``` + +- Replaced formatter actions with output sinks: `NoAction` -\> + `NullSink`, `Write` -\> `FileSink`, `ColorWriter` -\> + `ANSITerminalSink`. This improves naming consistency and shouldn\'t + affect client code unless these classes are used directly which + should be rarely needed. + +- Added `ThrowSystemError` function that formats a message and throws + `SystemError` containing the formatted message and system-specific + error description. For example, the following code + + ```c++ + FILE *f = fopen(filename, "r"); + if (!f) + ThrowSystemError(errno, "Failed to open file '{}'") << filename; + ``` + + will throw `SystemError` exception with description \"Failed to open + file \'\\': No such file or directory\" if file doesn\'t + exist. + +- Support for AppVeyor continuous integration platform. + +- `Format` now throws `SystemError` in case of I/O errors. + +- Improve test infrastructure. Print functions are now tested by + redirecting the output to a pipe. + +# 0.8.0 - 2014-04-14 + +- Initial release diff --git a/libs/openal-soft/fmt-11.1.1/doc/api.md b/libs/openal-soft/fmt-11.1.1/doc/api.md new file mode 100644 index 00000000..e86f0b06 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/api.md @@ -0,0 +1,673 @@ +# API Reference + +The {fmt} library API consists of the following components: + +- [`fmt/base.h`](#base-api): the base API providing main formatting functions + for `char`/UTF-8 with C++20 compile-time checks and minimal dependencies +- [`fmt/format.h`](#format-api): `fmt::format` and other formatting functions + as well as locale support +- [`fmt/ranges.h`](#ranges-api): formatting of ranges and tuples +- [`fmt/chrono.h`](#chrono-api): date and time formatting +- [`fmt/std.h`](#std-api): formatters for standard library types +- [`fmt/compile.h`](#compile-api): format string compilation +- [`fmt/color.h`](#color-api): terminal colors and text styles +- [`fmt/os.h`](#os-api): system APIs +- [`fmt/ostream.h`](#ostream-api): `std::ostream` support +- [`fmt/args.h`](#args-api): dynamic argument lists +- [`fmt/printf.h`](#printf-api): safe `printf` +- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support + +All functions and types provided by the library reside in namespace `fmt` +and macros have prefix `FMT_`. + +## Base API + +`fmt/base.h` defines the base API which provides main formatting functions +for `char`/UTF-8 with C++20 compile-time checks. It has minimal include +dependencies for better compile times. This header is only beneficial when +using {fmt} as a library (the default) and not in the header-only mode. +It also provides `formatter` specializations for the following types: + +- `int`, `long long`, +- `unsigned`, `unsigned long long` +- `float`, `double`, `long double` +- `bool` +- `char` +- `const char*`, [`fmt::string_view`](#basic_string_view) +- `const void*` + +The following functions use [format string syntax](syntax.md) similar to that +of [str.format](https://docs.python.org/3/library/stdtypes.html#str.format) +in Python. They take *fmt* and *args* as arguments. + +*fmt* is a format string that contains literal text and replacement fields +surrounded by braces `{}`. The fields are replaced with formatted arguments +in the resulting string. [`fmt::format_string`](#format_string) is a format +string which can be implicitly constructed from a string literal or a +`constexpr` string and is checked at compile time in C++20. To pass a runtime +format string wrap it in [`fmt::runtime`](#runtime). + +*args* is an argument list representing objects to be formatted. + +I/O errors are reported as [`std::system_error`]( +https://en.cppreference.com/w/cpp/error/system_error) exceptions unless +specified otherwise. + +::: print(format_string, T&&...) + +::: print(FILE*, format_string, T&&...) + +::: println(format_string, T&&...) + +::: println(FILE*, format_string, T&&...) + +::: format_to(OutputIt&&, format_string, T&&...) + +::: format_to_n(OutputIt, size_t, format_string, T&&...) + +::: format_to_n_result + +::: formatted_size(format_string, T&&...) + + +### Formatting User-Defined Types + +The {fmt} library provides formatters for many standard C++ types. +See [`fmt/ranges.h`](#ranges-api) for ranges and tuples including standard +containers such as `std::vector`, [`fmt/chrono.h`](#chrono-api) for date and +time formatting and [`fmt/std.h`](#std-api) for other standard library types. + +There are two ways to make a user-defined type formattable: providing a +`format_as` function or specializing the `formatter` struct template. + +Use `format_as` if you want to make your type formattable as some other +type with the same format specifiers. The `format_as` function should +take an object of your type and return an object of a formattable type. +It should be defined in the same namespace as your type. + +Example ([run](https://godbolt.org/z/nvME4arz8)): + + #include + + namespace kevin_namespacy { + + enum class film { + house_of_cards, american_beauty, se7en = 7 + }; + + auto format_as(film f) { return fmt::underlying(f); } + + } + + int main() { + fmt::print("{}\n", kevin_namespacy::film::se7en); // Output: 7 + } + +Using specialization is more complex but gives you full control over +parsing and formatting. To use this method specialize the `formatter` +struct template for your type and implement `parse` and `format` +methods. + +The recommended way of defining a formatter is by reusing an existing +one via inheritance or composition. This way you can support standard +format specifiers without implementing them yourself. For example: + +```c++ +// color.h: +#include + +enum class color {red, green, blue}; + +template <> struct fmt::formatter: formatter { + // parse is inherited from formatter. + + auto format(color c, format_context& ctx) const + -> format_context::iterator; +}; +``` + +```c++ +// color.cc: +#include "color.h" +#include + +auto fmt::formatter::format(color c, format_context& ctx) const + -> format_context::iterator { + string_view name = "unknown"; + switch (c) { + case color::red: name = "red"; break; + case color::green: name = "green"; break; + case color::blue: name = "blue"; break; + } + return formatter::format(name, ctx); +} +``` + +Note that `formatter::format` is defined in `fmt/format.h` +so it has to be included in the source file. Since `parse` is inherited +from `formatter` it will recognize all string format +specifications, for example + +```c++ +fmt::format("{:>10}", color::blue) +``` + +will return `" blue"`. + + + +In general the formatter has the following form: + + template <> struct fmt::formatter { + // Parses format specifiers and stores them in the formatter. + // + // [ctx.begin(), ctx.end()) is a, possibly empty, character range that + // contains a part of the format string starting from the format + // specifications to be parsed, e.g. in + // + // fmt::format("{:f} continued", ...); + // + // the range will contain "f} continued". The formatter should parse + // specifiers until '}' or the end of the range. In this example the + // formatter should parse the 'f' specifier and return an iterator + // pointing to '}'. + constexpr auto parse(format_parse_context& ctx) + -> format_parse_context::iterator; + + // Formats value using the parsed format specification stored in this + // formatter and writes the output to ctx.out(). + auto format(const T& value, format_context& ctx) const + -> format_context::iterator; + }; + +It is recommended to at least support fill, align and width that apply +to the whole object and have the same semantics as in standard +formatters. + +You can also write a formatter for a hierarchy of classes: + +```c++ +// demo.h: +#include +#include + +struct A { + virtual ~A() {} + virtual std::string name() const { return "A"; } +}; + +struct B : A { + virtual std::string name() const { return "B"; } +}; + +template +struct fmt::formatter, char>> : + fmt::formatter { + auto format(const A& a, format_context& ctx) const { + return formatter::format(a.name(), ctx); + } +}; +``` + +```c++ +// demo.cc: +#include "demo.h" +#include + +int main() { + B b; + A& a = b; + fmt::print("{}", a); // Output: B +} +``` + +Providing both a `formatter` specialization and a `format_as` overload is +disallowed. + +::: basic_format_parse_context + +::: context + +::: format_context + +### Compile-Time Checks + +Compile-time format string checks are enabled by default on compilers +that support C++20 `consteval`. On older compilers you can use the +[FMT_STRING](#legacy-checks) macro defined in `fmt/format.h` instead. + +Unused arguments are allowed as in Python's `str.format` and ordinary functions. + +See [Type Erasure](#type-erasure) for an example of how to enable compile-time +checks in your own functions with `fmt::format_string` while avoiding template +bloat. + +::: fstring + +::: format_string + +::: runtime(string_view) + +### Type Erasure + +You can create your own formatting function with compile-time checks and +small binary footprint, for example ([run](https://godbolt.org/z/b9Pbasvzc)): + +```c++ +#include + +void vlog(const char* file, int line, + fmt::string_view fmt, fmt::format_args args) { + fmt::print("{}: {}: {}", file, line, fmt::vformat(fmt, args)); +} + +template +void log(const char* file, int line, + fmt::format_string fmt, T&&... args) { + vlog(file, line, fmt, fmt::make_format_args(args...)); +} + +#define MY_LOG(fmt, ...) log(__FILE__, __LINE__, fmt, __VA_ARGS__) + +MY_LOG("invalid squishiness: {}", 42); +``` + +Note that `vlog` is not parameterized on argument types which improves +compile times and reduces binary code size compared to a fully +parameterized version. + +::: make_format_args(T&...) + +::: basic_format_args + +::: format_args + +::: basic_format_arg + +### Named Arguments + +::: arg(const Char*, const T&) + +Named arguments are not supported in compile-time checks at the moment. + +### Compatibility + +::: basic_string_view + +::: string_view + +## Format API + +`fmt/format.h` defines the full format API providing additional +formatting functions and locale support. + + +::: format(format_string, T&&...) + +::: vformat(string_view, format_args) + +::: operator""_a() + +### Utilities + +::: ptr(T) + +::: underlying(Enum) + +::: to_string(const T&) + +::: group_digits(T) + +::: detail::buffer + +::: basic_memory_buffer + +### System Errors + +{fmt} does not use `errno` to communicate errors to the user, but it may +call system functions which set `errno`. Users should not make any +assumptions about the value of `errno` being preserved by library +functions. + +::: system_error + +::: format_system_error + +### Custom Allocators + +The {fmt} library supports custom dynamic memory allocators. A custom +allocator class can be specified as a template argument to +[`fmt::basic_memory_buffer`](#basic_memory_buffer): + + using custom_memory_buffer = + fmt::basic_memory_buffer; + +It is also possible to write a formatting function that uses a custom +allocator: + + using custom_string = + std::basic_string, custom_allocator>; + + auto vformat(custom_allocator alloc, fmt::string_view fmt, + fmt::format_args args) -> custom_string { + auto buf = custom_memory_buffer(alloc); + fmt::vformat_to(std::back_inserter(buf), fmt, args); + return custom_string(buf.data(), buf.size(), alloc); + } + + template + auto format(custom_allocator alloc, fmt::string_view fmt, + const Args& ... args) -> custom_string { + return vformat(alloc, fmt, fmt::make_format_args(args...)); + } + +The allocator will be used for the output container only. Formatting +functions normally don't do any allocations for built-in and string +types except for non-default floating-point formatting that occasionally +falls back on `sprintf`. + +### Locale + +All formatting is locale-independent by default. Use the `'L'` format +specifier to insert the appropriate number separator characters from the +locale: + + #include + #include + + std::locale::global(std::locale("en_US.UTF-8")); + auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" + +`fmt/format.h` provides the following overloads of formatting functions +that take `std::locale` as a parameter. The locale type is a template +parameter to avoid the expensive `` include. + +::: format(detail::locale_ref, format_string, T&&...) + +::: format_to(OutputIt, detail::locale_ref, format_string, T&&...) + +::: formatted_size(detail::locale_ref, format_string, T&&...) + + +### Legacy Compile-Time Checks + +`FMT_STRING` enables compile-time checks on older compilers. It requires +C++14 or later and is a no-op in C++11. + +::: FMT_STRING + +To force the use of legacy compile-time checks, define the preprocessor +variable `FMT_ENFORCE_COMPILE_STRING`. When set, functions accepting +`FMT_STRING` will fail to compile with regular strings. + + +## Range and Tuple Formatting + +`fmt/ranges.h` provides formatting support for ranges and tuples: + + #include + + fmt::print("{}", std::tuple{'a', 42}); + // Output: ('a', 42) + +Using `fmt::join`, you can separate tuple elements with a custom separator: + + #include + + auto t = std::tuple{1, 'a'}; + fmt::print("{}", fmt::join(t, ", ")); + // Output: 1, a + +::: join(Range&&, string_view) + +::: join(It, Sentinel, string_view) + +::: join(std::initializer_list, string_view) + + +## Date and Time Formatting + +`fmt/chrono.h` provides formatters for + +- [`std::chrono::duration`](https://en.cppreference.com/w/cpp/chrono/duration) +- [`std::chrono::time_point`]( + https://en.cppreference.com/w/cpp/chrono/time_point) +- [`std::tm`](https://en.cppreference.com/w/cpp/chrono/c/tm) + +The format syntax is described in [Chrono Format Specifications](syntax.md# +chrono-format-specifications). + +**Example**: + + #include + + int main() { + std::time_t t = std::time(nullptr); + + fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + // Output: The date is 2020-11-07. + // (with 2020-11-07 replaced by the current date) + + using namespace std::literals::chrono_literals; + + fmt::print("Default format: {} {}\n", 42s, 100ms); + // Output: Default format: 42s 100ms + + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + // Output: strftime-like format: 03:15:30 + } + +::: localtime(std::time_t) + +::: gmtime(std::time_t) + + +## Standard Library Types Formatting + +`fmt/std.h` provides formatters for: + +- [`std::atomic`](https://en.cppreference.com/w/cpp/atomic/atomic) +- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) +- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) +- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) +- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception) +- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) +- [`std::monostate`]( + https://en.cppreference.com/w/cpp/utility/variant/monostate) +- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) +- [`std::source_location`]( + https://en.cppreference.com/w/cpp/utility/source_location) +- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) +- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) + +::: ptr(const std::unique_ptr&) + +::: ptr(const std::shared_ptr&) + +### Variants + +A `std::variant` is only formattable if every variant alternative is +formattable, and requires the `__cpp_lib_variant` [library +feature](https://en.cppreference.com/w/cpp/feature_test). + +**Example**: + + #include + + fmt::print("{}", std::variant('x')); + // Output: variant('x') + + fmt::print("{}", std::variant()); + // Output: variant(monostate) + +## Bit-Fields and Packed Structs + +To format a bit-field or a field of a struct with `__attribute__((packed))` +applied to it, you need to convert it to the underlying or compatible type via +a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)): + +```c++ +struct smol { + int bit : 1; +}; + +auto s = smol(); +fmt::print("{}", +s.bit); +``` + +This is a known limitation of "perfect" forwarding in C++. + + +## Format String Compilation + +`fmt/compile.h` provides format string compilation and compile-time +(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf` +user-defined literal defined in namespace `fmt::literals`. Format strings +marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into +efficient formatting code at compile-time. This supports arguments of built-in +and string types as well as user-defined types with `format` functions taking +the format context type as a template parameter in their `formatter` +specializations. For example: + + template <> struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx); + + template + auto format(const point& p, FormatContext& ctx) const; + }; + +Format string compilation can generate more binary code compared to the +default API and is only recommended in places where formatting is a +performance bottleneck. + +::: FMT_COMPILE + +::: operator""_cf + + +## Terminal Colors and Text Styles + +`fmt/color.h` provides support for terminal color and text style output. + +::: print(const text_style&, format_string, T&&...) + +::: fg(detail::color_type) + +::: bg(detail::color_type) + +::: styled(const T&, text_style) + + +## System APIs + +::: ostream + +::: windows_error + + +## `std::ostream` Support + +`fmt/ostream.h` provides `std::ostream` support including formatting of +user-defined types that have an overloaded insertion operator +(`operator<<`). In order to make a type formattable via `std::ostream` +you should provide a `formatter` specialization inherited from +`ostream_formatter`: + + #include + + struct date { + int year, month, day; + + friend std::ostream& operator<<(std::ostream& os, const date& d) { + return os << d.year << '-' << d.month << '-' << d.day; + } + }; + + template <> struct fmt::formatter : ostream_formatter {}; + + std::string s = fmt::format("The date is {}", date{2012, 12, 9}); + // s == "The date is 2012-12-9" + +::: streamed(const T&) + +::: print(std::ostream&, format_string, T&&...) + + +## Dynamic Argument Lists + +The header `fmt/args.h` provides `dynamic_format_arg_store`, a builder-like API +that can be used to construct format argument lists dynamically. + +::: dynamic_format_arg_store + + +## Safe `printf` + +The header `fmt/printf.h` provides `printf`-like formatting +functionality. The following functions use [printf format string +syntax](https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html) +with the POSIX extension for positional arguments. Unlike their standard +counterparts, the `fmt` functions are type-safe and throw an exception +if an argument type doesn't match its format specification. + +::: printf(string_view, const T&...) + +::: fprintf(std::FILE*, const S&, const T&...) + +::: sprintf(const S&, const T&...) + + +## Wide Strings + +The optional header `fmt/xchar.h` provides support for `wchar_t` and +exotic character types. + +::: is_char + +::: wstring_view + +::: wformat_context + +::: to_wstring(const T&) + +## Compatibility with C++20 `std::format` + +{fmt} implements nearly all of the [C++20 formatting +library](https://en.cppreference.com/w/cpp/utility/format) with the +following differences: + +- Names are defined in the `fmt` namespace instead of `std` to avoid + collisions with standard library implementations. +- Width calculation doesn't use grapheme clusterization. The latter has + been implemented in a separate branch but hasn't been integrated yet. diff --git a/libs/openal-soft/fmt-11.1.1/doc/fmt.css b/libs/openal-soft/fmt-11.1.1/doc/fmt.css new file mode 100644 index 00000000..994d6e2e --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/fmt.css @@ -0,0 +1,61 @@ +: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 */ + } +} diff --git a/libs/openal-soft/fmt-11.1.1/doc/fmt.js b/libs/openal-soft/fmt-11.1.1/doc/fmt.js new file mode 100644 index 00000000..da7e95b7 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/fmt.js @@ -0,0 +1,4 @@ +document$.subscribe(() => { + hljs.highlightAll(), + { language: 'c++' } +}) diff --git a/libs/openal-soft/fmt-11.1.1/doc/get-started.md b/libs/openal-soft/fmt-11.1.1/doc/get-started.md new file mode 100644 index 00000000..e61da882 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/get-started.md @@ -0,0 +1,222 @@ +# Get Started + +Compile and run {fmt} examples online with [Compiler Explorer]( +https://godbolt.org/z/P7h6cd6o3). + +{fmt} is compatible with any build system. The next section describes its usage +with CMake, while the [Build Systems](#build-systems) section covers the rest. + +## CMake + +{fmt} provides two CMake targets: `fmt::fmt` for the compiled library and +`fmt::fmt-header-only` for the header-only library. It is recommended to use +the compiled library for improved build times. + +There are three primary ways to use {fmt} with CMake: + +* **FetchContent**: Starting from CMake 3.11, you can use [`FetchContent`]( + https://cmake.org/cmake/help/v3.30/module/FetchContent.html) to automatically + download {fmt} as a dependency at configure time: + + include(FetchContent) + + FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 + FetchContent_MakeAvailable(fmt) + + target_link_libraries( fmt::fmt) + +* **Installed**: You can find and use an [installed](#installation) version of + {fmt} in your `CMakeLists.txt` file as follows: + + find_package(fmt) + target_link_libraries( fmt::fmt) + +* **Embedded**: You can add the {fmt} source tree to your project and include it + in your `CMakeLists.txt` file: + + add_subdirectory(fmt) + target_link_libraries( fmt::fmt) + +## Installation + +### Debian/Ubuntu + +To install {fmt} on Debian, Ubuntu, or any other Debian-based Linux +distribution, use the following command: + + apt install libfmt-dev + +### Homebrew + +Install {fmt} on macOS using [Homebrew](https://brew.sh/): + + brew install fmt + +### Conda + +Install {fmt} on Linux, macOS, and Windows with [Conda]( +https://docs.conda.io/en/latest/), using its [conda-forge package]( +https://github.com/conda-forge/fmt-feedstock): + + conda install -c conda-forge fmt + +### vcpkg + +Download and install {fmt} using the vcpkg package manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install fmt + + + +## Building from Source + +CMake works by generating native makefiles or project files that can be +used in the compiler environment of your choice. The typical workflow +starts with: + + mkdir build # Create a directory to hold the build output. + cd build + cmake .. # Generate native build scripts. + +run in the `fmt` repository. + +If you are on a Unix-like system, you should now see a Makefile in the +current directory. Now you can build the library by running `make`. + +Once the library has been built you can invoke `make test` to run the tests. + +You can control generation of the make `test` target with the `FMT_TEST` +CMake option. This can be useful if you include fmt as a subdirectory in +your project but don't want to add fmt's tests to your `test` target. + +To build a shared library set the `BUILD_SHARED_LIBS` CMake variable to `TRUE`: + + cmake -DBUILD_SHARED_LIBS=TRUE .. + +To build a static library with position-independent code (e.g. for +linking it into another shared library such as a Python extension), set the +`CMAKE_POSITION_INDEPENDENT_CODE` CMake variable to `TRUE`: + + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. + +After building the library you can install it on a Unix-like system by +running `sudo make install`. + +### Building the Docs + +To build the documentation you need the following software installed on +your system: + +- [Python](https://www.python.org/) +- [Doxygen](http://www.stack.nl/~dimitri/doxygen/) +- [MkDocs](https://www.mkdocs.org/) with `mkdocs-material`, `mkdocstrings`, + `pymdown-extensions` and `mike` + +First generate makefiles or project files using CMake as described in +the previous section. Then compile the `doc` target/project, for example: + + make doc + +This will generate the HTML documentation in `doc/html`. + +## Build Systems + +### build2 + +You can use [build2](https://build2.org), a dependency manager and a build +system, to use {fmt}. + +Currently this package is available in these package repositories: + +- for released and published versions. +- for unreleased or custom versions. + +**Usage:** + +- `build2` package name: `fmt` +- Library target name: `lib{fmt}` + +To make your `build2` project depend on `fmt`: + +- Add one of the repositories to your configurations, or in your + `repositories.manifest`, if not already there: + + : + role: prerequisite + location: https://pkg.cppget.org/1/stable + +- Add this package as a dependency to your `manifest` file (example + for version 10): + + depends: fmt ~10.0.0 + +- Import the target and use it as a prerequisite to your own target + using `fmt` in the appropriate `buildfile`: + + import fmt = fmt%lib{fmt} + lib{mylib} : cxx{**} ... $fmt + +Then build your project as usual with `b` or `bdep update`. + +### Meson + +[Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html) includes an `fmt` +package. + +**Usage:** + +- Install the `fmt` subproject from the WrapDB by running: + + meson wrap install fmt + + from the root of your project. + +- In your project's `meson.build` file, add an entry for the new subproject: + + fmt = subproject('fmt') + fmt_dep = fmt.get_variable('fmt_dep') + +- Include the new dependency object to link with fmt: + + my_build_target = executable( + 'name', 'src/main.cc', dependencies: [fmt_dep]) + +**Options:** + +If desired, {fmt} can be built as a static library, or as a header-only library. + +For a static build, use the following subproject definition: + + fmt = subproject('fmt', default_options: 'default_library=static') + fmt_dep = fmt.get_variable('fmt_dep') + +For the header-only version, use: + + fmt = subproject('fmt') + fmt_dep = fmt.get_variable('fmt_header_only_dep') + +### Android NDK + +{fmt} provides [Android.mk file]( +https://github.com/fmtlib/fmt/blob/master/support/Android.mk) that can be used +to build the library with [Android NDK]( +https://developer.android.com/tools/sdk/ndk/index.html). + +### Other + +To use the {fmt} library with any other build system, add +`include/fmt/base.h`, `include/fmt/format.h`, `include/fmt/format-inl.h`, +`src/format.cc` and optionally other headers from a [release archive]( +https://github.com/fmtlib/fmt/releases) or the [git repository]( +https://github.com/fmtlib/fmt) to your project, add `include` to include +directories and make sure `src/format.cc` is compiled and linked with your code. diff --git a/libs/openal-soft/fmt-11.1.1/doc/index.md b/libs/openal-soft/fmt-11.1.1/doc/index.md new file mode 100644 index 00000000..4f28e114 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/index.md @@ -0,0 +1,151 @@ +--- +hide: + - navigation + - toc +--- + +# A modern formatting library + +
+ +
+

Safety

+

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

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

+→ Learn more +
+ +
+

Extensibility

+

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

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

+→ Learn more +
+ +
+

Performance

+

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

+
+ +
+

Unicode support

+

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

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

+

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

+
+ +
+

Fast compilation

+

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

+

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

+ +→ Learn more +
+ +
+

Small binary footprint

+

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

+

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

+
+ +
+

Portability

+

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

+

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

+

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

+

+
+ +
+

Open source

+

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

+

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

+
+ +
diff --git a/libs/openal-soft/fmt-11.1.1/doc/perf.svg b/libs/openal-soft/fmt-11.1.1/doc/perf.svg new file mode 100644 index 00000000..7867a154 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/perf.svg @@ -0,0 +1 @@ +double to string02505007501,0001,2501,500ostringstreamostrstreamsprintfdoubleconvfmtTime (ns), smaller is better \ No newline at end of file diff --git a/libs/openal-soft/fmt-11.1.1/doc/python-license.txt b/libs/openal-soft/fmt-11.1.1/doc/python-license.txt new file mode 100644 index 00000000..88eed1f9 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/python-license.txt @@ -0,0 +1,290 @@ +A. 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. diff --git a/libs/openal-soft/fmt-11.1.1/doc/syntax.md b/libs/openal-soft/fmt-11.1.1/doc/syntax.md new file mode 100644 index 00000000..46d7d2fd --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/doc/syntax.md @@ -0,0 +1,886 @@ +# 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: + + + + + + + + + + + + + + + + + + +
OptionMeaning
'<' + 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: + + + + + + + + + + + + + + + + + + +
OptionMeaning
'+' + 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: + + + + + + + + + + + + + + + + + + +
TypeMeaning
's' + String format. This is the default type for strings and may be omitted. +
'?'Debug format. The string is quoted and special characters escaped.
noneThe same as 's'.
+ +The available character presentation types are: + + + + + + + + + + + + + + + + + + +
TypeMeaning
'c' + Character format. This is the default type for characters and may be + omitted. +
'?'Debug format. The character is quoted and special characters escaped.
noneThe same as 'c'.
+ +The available integer presentation types are: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'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. +
noneThe 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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'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: + + + + + + + + + + + + + + +
TypeMeaning
'p' + Pointer format. This is the default type for pointers and may be omitted. +
noneThe 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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeMeaning
'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" +``` diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/args.h b/libs/openal-soft/fmt-11.1.1/include/fmt/args.h new file mode 100644 index 00000000..3ff47880 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/args.h @@ -0,0 +1,220 @@ +// 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_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/base.h b/libs/openal-soft/fmt-11.1.1/include/fmt/base.h new file mode 100644 index 00000000..42b7a555 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/base.h @@ -0,0 +1,2954 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) +# define FMT_MODULE +#endif + +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // memcmp + +# include // std::enable_if +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 110101 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L +// GCC only allows throw in constexpr since version 6: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#if !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_USE_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_USE_EXCEPTIONS 0 +#elif defined(__clang__) && !defined(__cpp_exceptions) +# define FMT_USE_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_USE_EXCEPTIONS 0 +#else +# define FMT_USE_EXCEPTIONS 1 +#endif +#if FMT_USE_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef FMT_NO_UNIQUE_ADDRESS +// Use the provided definition. +#elif FMT_CPLUSPLUS < 202002L +// Not supported. +#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifdef FMT_NODISCARD +// Use the provided definition. +#elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +#else +# define FMT_NODISCARD +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +#ifdef FMT_ALWAYS_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. +#ifdef NDEBUG +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +// Detect pragmas. +#define FMT_PRAGMA_IMPL(x) _Pragma(#x) +#if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) +#else +# define FMT_PRAGMA_GCC(x) +#endif +#if FMT_CLANG_VERSION +# define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) +#else +# define FMT_PRAGMA_CLANG(x) +#endif +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_WARNING(...) +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v11_alsoft { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#ifdef _WIN32 +# define FMT_WIN32 1 +#else +# define FMT_WIN32 0 +#endif + +#if !defined(FMT_HEADER_ONLY) && FMT_WIN32 +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_OPTIMIZE_SIZE +# define FMT_OPTIMIZE_SIZE 0 +#endif + +// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher +// per-call binary size by passing built-in types through the extension API. +#ifndef FMT_BUILTIN_TYPES +# define FMT_BUILTIN_TYPES 1 +#endif + +#define FMT_APPLY_VARIADIC(expr) \ + using ignore = int[]; \ + (void)ignore { 0, (expr, 0)... } + +// Enable minimal optimizations for more compact code in debug mode. +FMT_PRAGMA_GCC(push_options) +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) +FMT_PRAGMA_GCC(optimize("Og")) +#endif +FMT_PRAGMA_CLANG(diagnostic push) + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; +template using decay_t = typename std::decay::type; +using nullptr_t = decltype(nullptr); + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.9 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +template constexpr auto min_of(T a, T b) -> T { + return a < b ? a : b; +} +template constexpr auto max_of(T a, T b) -> T { + return a > b ? a : b; +} + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between clang 14 and libstdc++ consteval-based +// std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { + return val; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Use the provided definition. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +inline auto map(int128_opt x) -> int128_opt { return x; } +inline auto map(uint128_opt x) -> uint128_opt { return x; } +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +inline auto map(int128_opt) -> monostate { return {}; } +inline auto map(uint128_opt) -> monostate { return {}; } +#endif + +#ifndef FMT_USE_BITINT +# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500) +#endif + +#if FMT_USE_BITINT +FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") +template using bitint = _BitInt(N); +template using ubitint = unsigned _BitInt(N); +#else +template struct bitint {}; +template struct ubitint {}; +#endif // FMT_USE_BITINT + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +template +using unsigned_char = conditional_t; + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::is_convertible().data()), + const typename T::value_type*> {}; + +// Check if the literal encoding is UTF-8. +enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; +enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +static_assert(!FMT_UNICODE || use_utf8, + "Unicode support requires compiling with /utf-8"); + +template constexpr const char* narrow(const T*) { return nullptr; } +constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) + -> int { + if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +namespace adl { +using namespace std; + +template +auto invoke_back_inserter() + -> decltype(back_inserter(std::declval())); +} // namespace adl + +template +struct is_back_insert_iterator : std::false_type {}; + +template +struct is_back_insert_iterator< + It, bool_constant()), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> + typename OutputIt::container_type& { + struct accessor : OutputIt { + FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Parsing-related public API and forward declarations. +FMT_BEGIN_EXPORT + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string reference object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(nullptr_t) = delete; + + /// Constructs a string reference object from a C string. +#if FMT_GCC_VERSION + FMT_ALWAYS_INLINE +#endif + FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { +#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION + if (std::is_same::value) { + size_ = __builtin_strlen(detail::narrow(s)); + return; + } +#endif + size_t len = 0; + while (*s++) ++len; + size_ = len; + } + + /// Constructs a string reference from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + int result = + detail::compare(data_, other.data_, min_of(size_, other.size_)); + if (result != 0) return result; + return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view; + +/// Specifies if `T` is an extended character type. Can be specialized by users. +template struct is_xchar : std::false_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +#ifdef __cpp_char8_t +template <> struct is_xchar : std::true_type {}; +#endif + +// DEPRECATED! Will be replaced with an alias to prevent specializations. +template struct is_char : is_xchar {}; +template <> struct is_char : std::true_type {}; + +template class basic_appender; +using appender = basic_appender; + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +class context; +template class generic_context; +template class parse_context; + +// Longer aliases for C++20 compatibility. +template using basic_format_parse_context = parse_context; +using format_parse_context = parse_context; +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = + conditional_t::value, context, + generic_context, Char>>; + +template class basic_format_arg; +template class basic_format_args; + +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// A formatter for objects of type T. +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +enum class align { none, left, right, center, numeric }; +enum class sign { none, minus, plus, space }; +enum class arg_id_kind { none, index, name }; + +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Data is arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + size_t data_ = 1 << fill_size_shift; + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(fmt::align a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> arg_id_kind { + return static_cast((data_ & width_mask) >> width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr bool dynamic() const { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(fmt::sign s) { + data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8) | + (static_cast(fill_data_[2]) << 16)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + fill_data_[2] = static_cast(uchar >> 16); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } +}; + +// Format specifiers for built-in and string types. +struct format_specs : basic_specs { + int width; + int precision; + + constexpr format_specs() : width(0), precision(-1) {} +}; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + */ +template class parse_context { + private: + basic_string_view fmt_; + int next_arg_id_; + + enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; + + FMT_CONSTEXPR void do_check_arg_id(int arg_id); + + public: + using char_type = Char; + using iterator = const Char*; + + constexpr explicit parse_context(basic_string_view fmt, + int next_arg_id = 0) + : fmt_(fmt), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return fmt_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + fmt_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +FMT_END_EXPORT + +namespace detail { + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +constexpr auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const T& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +/// String's character (code unit) type. detail:: is intentional to prevent ADL. +template ()))> +using char_t = typename V::value_type; + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + +struct view {}; + +template struct named_arg; +template struct is_named_arg : std::false_type {}; +template struct is_static_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template struct named_arg : view { + const Char* name; + const T& value; + + named_arg(const Char* n, const T& v) : name(n), value(v) {} + static_assert(!is_named_arg::value, "nested named arguments"); +}; + +template constexpr auto count() -> int { return B ? 1 : 0; } +template constexpr auto count() -> int { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> int { + return count::value...>(); +} +template constexpr auto count_static_named_args() -> int { + return count::value...>(); +} + +template struct named_arg_info { + const Char* name; + int id; +}; + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, + int&) { + ++arg_index; +} +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, + int& arg_index, int& named_arg_index) { + named_args[named_arg_index++] = {T::name, arg_index++}; +} + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template +using format_as_result = + remove_cvref_t()))>; +template +using format_as_member_result = + remove_cvref_t::format_as(std::declval()))>; + +template +struct use_format_as : std::false_type {}; +// format_as member is only used to avoid injection into the std namespace. +template +struct use_format_as_member : std::false_type {}; + +// Only map owning types because mapping views can be unsafe. +template +struct use_format_as< + T, bool_constant>::value>> + : std::true_type {}; +template +struct use_format_as_member< + T, bool_constant>::value>> + : std::true_type {}; + +template > +using use_formatter = + bool_constant<(std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_array::value) && + !has_to_string_view::value && !is_named_arg::value && + !use_format_as::value && !use_format_as_member::value>; + +template > +auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) + -> decltype(formatter().format(*p, *ctx), std::true_type()); +template auto has_formatter_impl(...) -> std::false_type; + +// T can be const-qualified to check if it is const-formattable. +template constexpr auto has_formatter() -> bool { + return decltype(has_formatter_impl(static_cast(nullptr)))::value; +} + +// Maps formatting argument types to natively supported types or user-defined +// types with formatters. Returns void on errors to be SFINAE-friendly. +template struct type_mapper { + static auto map(signed char) -> int; + static auto map(unsigned char) -> unsigned; + static auto map(short) -> int; + static auto map(unsigned short) -> unsigned; + static auto map(int) -> int; + static auto map(unsigned) -> unsigned; + static auto map(long) -> long_type; + static auto map(unsigned long) -> ulong_type; + static auto map(long long) -> long long; + static auto map(unsigned long long) -> unsigned long long; + static auto map(int128_opt) -> int128_opt; + static auto map(uint128_opt) -> uint128_opt; + static auto map(bool) -> bool; + + template + static auto map(bitint) -> conditional_t; + template + static auto map(ubitint) + -> conditional_t; + + template ::value)> + static auto map(T) -> conditional_t< + std::is_same::value || std::is_same::value, Char, void>; + + static auto map(float) -> float; + static auto map(double) -> double; + static auto map(long double) -> long double; + + static auto map(Char*) -> const Char*; + static auto map(const Char*) -> const Char*; + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + static auto map(const T&) -> conditional_t::value, + basic_string_view, void>; + + static auto map(void*) -> const void*; + static auto map(const void*) -> const void*; + static auto map(volatile void*) -> const void*; + static auto map(const volatile void*) -> const void*; + static auto map(nullptr_t) -> const void*; + template ::value || + std::is_member_pointer::value)> + static auto map(const T&) -> void; + + template ::value)> + static auto map(const T& x) -> decltype(map(format_as(x))); + template ::value)> + static auto map(const T& x) -> decltype(map(formatter::format_as(x))); + + template ::value)> + static auto map(T&) -> conditional_t(), T&, void>; + + template ::value)> + static auto map(const T& named_arg) -> decltype(map(named_arg.value)); +}; + +// detail:: is used to workaround a bug in MSVC 2017. +template +using mapped_t = decltype(detail::type_mapper::map(std::declval())); + +// A type constant after applying type_mapper. +template +using mapped_type_constant = type_constant, Char>; + +template ::value> +using stored_type_constant = std::integral_constant< + type, Context::builtin_types || TYPE == type::int_type ? TYPE + : type::custom_type>; +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public parse_context { + private: + int num_args_; + const type* types_; + using base = parse_context; + + public: + FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, + int num_args, const type* types, + int next_arg_id = 0) + : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +// An argument reference. +template union arg_ref { + FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} + FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align { + switch (c) { + case '<': return align::left; + case '>': return align::right; + case '^': return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template struct dynamic_spec_handler { + parse_context& ctx; + arg_ref& ref; + arg_id_kind& kind; + + FMT_CONSTEXPR void on_index(int id) { + ref = id; + kind = arg_id_kind::index; + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = id; + kind = arg_id_kind::name; + ctx.check_arg_id(id); + } +}; + +template struct parse_dynamic_spec_result { + const Char* end; + arg_id_kind kind; +}; + +// Parses integer | "{" [arg_id] "}". +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + parse_context& ctx) + -> parse_dynamic_spec_result { + FMT_ASSERT(begin != end, ""); + auto kind = arg_id_kind::none; + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val == -1) report_error("number is too big"); + value = val; + } else { + if (*begin == '{') { + ++begin; + if (begin != end) { + Char c = *begin; + if (c == '}' || c == ':') { + int id = ctx.next_arg_id(); + ref = id; + kind = arg_id_kind::index; + ctx.check_dynamic_spec(id); + } else { + begin = parse_arg_id(begin, end, + dynamic_spec_handler{ctx, ref, kind}); + } + } + if (begin != end && *begin == '}') return {++begin, kind}; + } + report_error("invalid format string"); + } + return {begin, kind}; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + format_specs& specs, arg_ref& width_ref, + parse_context& ctx) -> const Char* { + auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + specs.set_dynamic_width(result.kind); + return result.end; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + format_specs& specs, + arg_ref& precision_ref, + parse_context& ctx) -> const Char* { + ++begin; + if (begin == end) { + report_error("invalid precision"); + return begin; + } + auto result = + parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); + specs.set_dynamic_precision(result.kind); + return result.end; +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + parse_context& ctx, type arg_type) + -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) report_error("invalid format specifier"); + specs.set_type(pres_type); + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.set_align(parse_align(c)); + ++begin; + break; + case '+': + case ' ': + specs.set_sign(c == ' ' ? sign::space : sign::plus); + FMT_FALLTHROUGH; + case '-': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.set_alt(); + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + report_error("format specifier requires numeric argument"); + if (specs.align() == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.set_align(align::numeric); + specs.set_fill('0'); + } + ++begin; + break; + // clang-format off + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '{': + // clang-format on + enter_state(state::width); + begin = parse_width(begin, end, specs, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.set_localized(); + ++begin; + break; + case 'd': return parse_presentation_type(pres::dec, integral_set); + case 'X': specs.set_upper(); FMT_FALLTHROUGH; + case 'x': return parse_presentation_type(pres::hex, integral_set); + case 'o': return parse_presentation_type(pres::oct, integral_set); + case 'B': specs.set_upper(); FMT_FALLTHROUGH; + case 'b': return parse_presentation_type(pres::bin, integral_set); + case 'E': specs.set_upper(); FMT_FALLTHROUGH; + case 'e': return parse_presentation_type(pres::exp, float_set); + case 'F': specs.set_upper(); FMT_FALLTHROUGH; + case 'f': return parse_presentation_type(pres::fixed, float_set); + case 'G': specs.set_upper(); FMT_FALLTHROUGH; + case 'g': return parse_presentation_type(pres::general, float_set); + case 'A': specs.set_upper(); FMT_FALLTHROUGH; + case 'a': return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto alignment = parse_align(to_ascii(*fill_end)); + enter_state(state::align, alignment != align::none); + specs.set_fill( + basic_string_view(begin, to_unsigned(fill_end - begin))); + specs.set_align(alignment); + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, + const Char* end, + Handler&& handler) + -> const Char* { + ++begin; + if (begin == end) { + handler.on_error("invalid format string"); + return end; + } + int arg_id = 0; + switch (*begin) { + case '}': + handler.on_replacement_field(handler.on_arg_id(), begin); + return begin + 1; + case '{': handler.on_text(begin, begin + 1); return begin + 1; + case ':': arg_id = handler.on_arg_id(); break; + default: { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + } adapter = {handler, 0}; + begin = parse_arg_id(begin, end, adapter); + arg_id = adapter.arg_id; + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(arg_id, begin); + return begin + 1; + } + if (c != ':') { + handler.on_error("missing '}' in format string"); + return end; + } + break; + } + } + begin = handler.on_format_specs(arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, + Handler&& handler) { + auto begin = fmt.data(), end = begin + fmt.size(); + auto p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + auto type = specs.type(); + if (type != presentation_type::none && type != presentation_type::chr && + type != presentation_type::debug) { + return false; + } + if (specs.align() == align::numeric || specs.sign() != sign::none || + specs.alt()) { + report_error("invalid format specifier for char"); + } + return true; +} + +// A base class for compile-time strings. +struct compile_string {}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { + using mapped_type = remove_cvref_t>; + constexpr bool formattable = + std::is_constructible>::value; + if (!formattable) return ctx.begin(); // Error is reported in the value ctor. + using formatted_type = conditional_t; + return formatter().parse(ctx); +} + +template struct arg_pack {}; + +template +class format_string_checker { + private: + type types_[max_of(1, NUM_ARGS)]; + named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; + compile_parse_context context_; + + using parse_func = auto (*)(parse_context&) -> const Char*; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; + + public: + template + FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, + arg_pack) + : types_{mapped_type_constant::value...}, + named_args_{}, + context_(fmt, NUM_ARGS, types_), + parse_funcs_{&invoke_parse...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_static_named_arg(named_args_, arg_index, named_arg_index)); + ignore_unused(arg_index, named_arg_index); + } + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + context_.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + for (int i = 0; i < NUM_NAMED_ARGS; ++i) { + if (named_args_[i].name == id) return named_args_[i].id; + } + if (!DYNAMIC_NAMES) on_error("argument not found"); + return -1; + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + context_.advance_to(begin); + if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); + while (begin != end && *begin != '}') ++begin; + return begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + FMT_CONSTEXPR void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = min_of(count, capacity_); + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template +// Workaround for MSVC2019 to fix error C2893: Failed to specialize function +// template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 + FMT_CONSTEXPR20 +#endif + void + append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + // A loop is faster than memcpy on small sizes. + T* out = ptr_ + size_; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + constexpr explicit buffer_traits(size_t) {} + constexpr auto count() const -> size_t { return 0; } + constexpr auto limit(size_t size) const -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + constexpr auto count() const -> size_t { return count_; } + FMT_CONSTEXPR auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return min_of(size, n); + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +template +class container_buffer : public buffer { + private: + using value_type = typename Container::value_type; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container.resize(capacity); + self.set(&self.container[0], capacity); + } + + public: + Container& container; + + explicit container_buffer(Container& c) + : buffer(grow, c.size()), container(c) {} +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public container_buffer { + private: + using base = container_buffer; + + public: + explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : base(get_container(out)) {} + + auto out() -> OutputIt { return OutputIt(this->container); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + constexpr auto count() const noexcept -> size_t { + return count_ + this->size(); + } +}; + +template +struct is_back_insert_iterator> : std::true_type {}; + +template +struct has_back_insert_iterator_container_append : std::false_type {}; +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template ::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template ::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +struct is_buffer_appender : std::false_type {}; +template +struct is_buffer_appender< + It, bool_constant< + is_back_insert_iterator::value && + std::is_base_of, + typename It::container_type>::value>> + : std::true_type {}; + +// Maps an output iterator to a buffer. +template ::value)> +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template ::value)> +auto get_buffer(OutputIt out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +// This type is intentionally undefined, only used for errors. +template struct type_is_unformattable_for; + +template struct string_value { + const Char* data; + size_t size; + auto str() const -> basic_string_view { return {data, size}; } +}; + +template struct custom_value { + using char_type = typename Context::char_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +struct custom_tag {}; + +#if !FMT_BUILTIN_TYPES +# define FMT_BUILTIN , monostate +#else +# define FMT_BUILTIN +#endif + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(signed char x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(signed short x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(int x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} + FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} + FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) + : value(ulong_type(x)) {} + constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} + constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) + : ulong_long_value(x) {} + FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} + FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} + constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} + + template + constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + template + constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + + template ::value)> + constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { + static_assert( + std::is_same::value || std::is_same::value, + "mixing character types is disallowed"); + } + + constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} + constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} + FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} + + FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { + static_assert(std::is_same::value, + "mixing character types is disallowed"); + auto sv = to_string_view(x); + string.data = sv.data(); + string.size = sv.size(); + } + FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(const volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(nullptr_t) : pointer(nullptr) {} + + template ::value || + std::is_member_pointer::value)> + value(const T&) { + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + static_assert(sizeof(T) == 0, + "formatting of non-void pointers is disallowed"); + } + + template ::value)> + value(const T& x) : value(format_as(x)) {} + template ::value)> + value(const T& x) : value(formatter::format_as(x)) {} + + template ::value)> + value(const T& named_arg) : value(named_arg.value) {} + + template ::value || !FMT_BUILTIN_TYPES)> + FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} + + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + private: + template ())> + FMT_CONSTEXPR value(T& x, custom_tag) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. + if (!is_constant_evaluated()) { + custom.value = + const_cast(&reinterpret_cast(x)); + } else { + custom.value = nullptr; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same*>::value) + custom.value = const_cast(&x); +#endif + } + custom.format = format_custom>; + } + + template ())> + FMT_CONSTEXPR value(const T&, custom_tag) { + // Cannot format an argument; to make type T formattable provide a + // formatter specialization: https://fmt.dev/latest/api.html#udt. + type_is_unformattable_for _; + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom(void* arg, parse_context& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, + void_t&>()++ = std::declval())>> + : std::true_type {}; + +#ifndef FMT_USE_LOCALE +# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) +#endif + +// A type-erased reference to an std::locale to avoid a heavy include. +struct locale_ref { +#if FMT_USE_LOCALE + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + + template + locale_ref(const Locale& loc); + + inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#endif // FMT_USE_LOCALE + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(stored_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr auto make_descriptor() -> unsigned long long { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template +struct named_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + arg_t args[1 + NUM_ARGS]; + named_arg_info named_args[NUM_NAMED_ARGS]; + + template + FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, values...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_named_arg(named_args, arg_index, named_arg_index, values)); + } + + named_arg_store(named_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) + args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + named_arg_store(const named_arg_store& rhs) = delete; + named_arg_store& operator=(const named_arg_store& rhs) = delete; + named_arg_store& operator=(named_arg_store&& rhs) = delete; + operator const arg_t*() const { return args + 1; } +}; + +// An array of references to arguments. It can be implicitly converted to +// `basic_format_args` for passing into type-erased formatting functions +// such as `vformat`. It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + using type = + conditional_t[max_of(1, NUM_ARGS)], + named_arg_store>; + type args; +}; + +// TYPE can be different from type_constant, e.g. for __float128. +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.set_type(set ? presentation_type::debug : presentation_type::none); + } + + FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +template +struct locking + : bool_constant::value == type::custom_type> {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc = {}); + +#if FMT_WIN32 +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); +#else // format_args is passed by reference since it is defined later. +inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} +#endif +} // namespace detail + +// The main public API. + +template +FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { + // Argument id is only checked at compile time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && use_constexpr_cast) { + auto ctx = static_cast*>(this); + if (arg_id >= ctx->num_args()) report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { + using detail::compile_parse_context; + if (detail::is_constant_evaluated() && use_constexpr_cast) + static_cast*>(this)->check_dynamic_spec(arg_id); +} + +FMT_BEGIN_EXPORT + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + protected: + detail::buffer* container; + + public: + using container_type = detail::buffer; + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} + + FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { + container->push_back(c); + return *this; + } + FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } +}; + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + friend class basic_format_args; + + using char_type = typename Context::char_type; + + public: + class handle { + private: + detail::custom_value custom_; + + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(parse_context& parse_ctx, Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + template + basic_format_arg(T&& val) + : value_(val), type_(detail::stored_type_constant::value) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + auto type() const -> detail::type { return type_; } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { + using detail::map; + switch (type_) { + case detail::type::none_type: break; + case detail::type::int_type: return vis(value_.int_value); + case detail::type::uint_type: return vis(value_.uint_value); + case detail::type::long_long_type: return vis(value_.long_long_value); + case detail::type::ulong_long_type: return vis(value_.ulong_long_value); + case detail::type::int128_type: return vis(map(value_.int128_value)); + case detail::type::uint128_type: return vis(map(value_.uint128_value)); + case detail::type::bool_type: return vis(value_.bool_value); + case detail::type::char_type: return vis(value_.char_value); + case detail::type::float_type: return vis(value_.float_value); + case detail::type::double_type: return vis(value_.double_value); + case detail::type::long_double_type: return vis(value_.long_double_value); + case detail::type::cstring_type: return vis(value_.string.data); + case detail::type::string_type: return vis(value_.string.str()); + case detail::type::pointer_type: return vis(value_.pointer); + case detail::type::custom_type: return vis(handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + parse_context& parse_ctx, Context& ctx) + -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const basic_format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + template + using store = + detail::format_arg_store; + + public: + using format_arg = basic_format_arg; + + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + values_(s.args) {} + + template detail::max_packed_args)> + constexpr basic_format_args(const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + args_(s.args) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count, + bool has_named = false) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | + (has_named ? +detail::has_named_args_bit : 0)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + auto arg = format_arg(); + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + format_args args_; + FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; + + public: + /// The character type for the output. + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using parse_context_type FMT_DEPRECATED = parse_context<>; + template using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + /// Constructs a `context` object. References to the arguments are stored + /// in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + inline auto arg(string_view name) const -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(string_view name) const -> int { + return args_.get_id(name); + } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() const -> iterator { return out_; } + + // Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } + +/// A compile-time format string. Use `format_string` in the public API to +/// prevent type deduction. +template struct fstring { + private: + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + string_view str; + using t = fstring; + + // Reports a compile-time error if S is not a valid format string for T. + template + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { + using namespace detail; + static_assert(count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); + if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { + auto sv = string_view(str); + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(sv, checker(sv, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { + FMT_CONSTEXPR auto sv = string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + fstring(runtime_format_string<> fmt) : str(fmt.str) {} + + // Returning by reference generates better code in debug mode. + FMT_ALWAYS_INLINE operator const string_view&() const { return str; } + auto get() const -> string_view { return str; } +}; + +template using format_string = typename fstring::t; + +template +using is_formattable = bool_constant::value, int*, T>, Char>, + void>::value>; +#ifdef __cpp_concepts +template +concept formattable = is_formattable, Char>::value; +#endif + +template +using has_formatter FMT_DEPRECATED = std::is_constructible>; + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor()> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + // Suppress warnings for pathological types convertible to detail::value. + FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") + return {{args...}}; +} + +template +using vargs = + detail::format_arg_store(), + detail::make_descriptor()>; + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function. + * + * **Example**: + * + * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + return {name, arg}; +} + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(out, fmt.str, vargs{{args...}}); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt.str, vargs{{args...}}); +} + +struct format_to_result { + /// Pointer to just after the last successful write in the array. + char* out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator char*() const { + // Report truncation to prevent silent data loss. + if (truncated) report_error("output is truncated"); + return out; + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(stdout, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(f, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + return detail::const_check(detail::use_utf8) + ? vprintln(f, fmt.str, va) + : detail::vprint_mojibake(f, fmt.str, va, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_END_EXPORT +FMT_PRAGMA_CLANG(diagnostic pop) +FMT_PRAGMA_GCC(pop_options) +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/chrono.h b/libs/openal-soft/fmt-11.1.1/include/fmt/chrono.h new file mode 100644 index 00000000..abf3671e --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/chrono.h @@ -0,0 +1,2338 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#ifndef FMT_MODULE +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +namespace fmt_detail { +struct time_zone { + template + auto to_sys(T) + -> std::chrono::time_point { + return {}; + } +}; +template inline auto current_zone(T...) -> time_zone* { + return nullptr; +} + +template inline void _tzset(T...) {} +} // namespace fmt_detail + +FMT_BEGIN_NAMESPACE + +// Enable safe chrono durations, unless explicitly disabled. +#ifndef FMT_SAFE_DURATION_CAST +# define FMT_SAFE_DURATION_CAST 1 +#endif +#if FMT_SAFE_DURATION_CAST + +// For conversion between std::chrono::durations without undefined +// behaviour or erroneous results. +// This is a stripped down version of duration_cast, for inclusion in fmt. +// See https://github.com/pauldreik/safe_duration_cast +// +// Copyright Paul Dreik 2019 +namespace safe_duration_cast { + +template ::value && + std::numeric_limits::is_signed == + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + // A and B are both signed, or both unsigned. + if (detail::const_check(F::digits <= T::digits)) { + // From fits in To without any problem. + } else { + // From does not always fit in To, resort to a dynamic check. + if (from < (T::min)() || from > (T::max)()) { + // outside range. + ec = 1; + return {}; + } + } + return static_cast(from); +} + +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. +template ::value && + std::numeric_limits::is_signed != + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + if (detail::const_check(F::is_signed && !T::is_signed)) { + // From may be negative, not allowed! + if (fmt::detail::is_negative(from)) { + ec = 1; + return {}; + } + // From is positive. Can it always fit in To? + if (detail::const_check(F::digits > T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + } + + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + return static_cast(from); // Lossless conversion. +} + +template ::value)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + return from; +} // function + +// clang-format off +/** + * converts From to To if possible, otherwise ec is set. + * + * input | output + * ---------------------------------|--------------- + * NaN | NaN + * Inf | Inf + * normal, fits in output | converted (possibly lossy) + * normal, does not fit in output | ec is set + * subnormal | best effort + * -Inf | -Inf + */ +// clang-format on +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + using T = std::numeric_limits; + static_assert(std::is_floating_point::value, "From must be floating"); + static_assert(std::is_floating_point::value, "To must be floating"); + + // catch the only happy case + if (std::isfinite(from)) { + if (from >= T::lowest() && from <= (T::max)()) { + return static_cast(from); + } + // not within range. + ec = 1; + return {}; + } + + // nan and inf will be preserved + return static_cast(from); +} // function + +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + static_assert(std::is_floating_point::value, "From must be floating"); + return from; +} + +/// Safe duration_cast between floating point durations +template ::value), + FMT_ENABLE_IF(std::is_floating_point::value)> +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { + using From = std::chrono::duration; + ec = 0; + if (std::isnan(from.count())) { + // nan in, gives nan out. easy. + return To{std::numeric_limits::quiet_NaN()}; + } + // maybe we should also check if from is denormal, and decide what to do about + // it. + + // +-inf should be preserved. + if (std::isinf(from.count())) { + return To{from.count()}; + } + + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type::type; + + // force conversion of From::rep -> IntermediateRep to be safe, + // even if it will never happen be narrowing in this context. + IntermediateRep count = + safe_float_conversion(from.count(), ec); + if (ec) { + return {}; + } + + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + constexpr auto max1 = detail::max_value() / + static_cast(Factor::num); + if (count > max1) { + ec = 1; + return {}; + } + constexpr auto min1 = std::numeric_limits::lowest() / + static_cast(Factor::num); + if (count < min1) { + ec = 1; + return {}; + } + count *= static_cast(Factor::num); + } + + // this can't go wrong, right? den>0 is checked earlier. + if (detail::const_check(Factor::den != 1)) { + using common_t = typename std::common_type::type; + count /= static_cast(Factor::den); + } + + // convert to the to type, safely + using ToRep = typename To::rep; + + const ToRep tocount = safe_float_conversion(count, ec); + if (ec) { + return {}; + } + return To{tocount}; +} +} // namespace safe_duration_cast +#endif + +namespace detail { + +// Check if std::chrono::utc_time is available. +#ifdef FMT_USE_UTC_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_UTC_TIME 0 +#endif +#if FMT_USE_UTC_TIME +using utc_clock = std::chrono::utc_clock; +#else +struct utc_clock { + void to_sys(); +}; +#endif + +// Check if std::chrono::local_time is available. +#ifdef FMT_USE_LOCAL_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_LOCAL_TIME 0 +#endif +#if FMT_USE_LOCAL_TIME +using local_t = std::chrono::local_t; +#else +struct local_t {}; +#endif + +} // namespace detail + +template +using sys_time = std::chrono::time_point; + +template +using utc_time = std::chrono::time_point; + +template +using local_time = std::chrono::time_point; + +namespace detail { + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +template struct null {}; +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public StreamBuf { + private: + using char_type = typename StreamBuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename StreamBuf::int_type; + using traits_type = typename StreamBuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; + +template +void write_codecvt(codecvt_result& out, string_view in, + const std::locale& loc) { + FMT_PRAGMA_CLANG(diagnostic push) + FMT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated") + auto& f = std::use_facet>(loc); + FMT_PRAGMA_CLANG(diagnostic pop) + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf), + std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::use_utf8 && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; +#else + using code_unit = char32_t; +#endif + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy(u.c_str(), u.c_str() + u.size(), out); + } + return copy(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +FMT_NORETURN inline void throw_duration_error() { + FMT_THROW(format_error("cannot format duration")); +} + +// Cast one integral duration to another with an overflow check. +template ::value&& + std::is_integral::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if !FMT_SAFE_DURATION_CAST + return std::chrono::duration_cast(from); +#else + // The conversion factor: to.count() == factor * from.count(). + using factor = std::ratio_divide; + + using common_rep = typename std::common_type::type; + + int ec = 0; + auto count = safe_duration_cast::lossless_integral_conversion( + from.count(), ec); + if (ec) throw_duration_error(); + + // Multiply from.count() by factor and check for overflow. + if (const_check(factor::num != 1)) { + if (count > max_value() / factor::num) throw_duration_error(); + const auto min = (std::numeric_limits::min)() / factor::num; + if (const_check(!std::is_unsigned::value) && count < min) + throw_duration_error(); + count *= factor::num; + } + if (const_check(factor::den != 1)) count /= factor::den; + auto to = + To(safe_duration_cast::lossless_integral_conversion( + count, ec)); + if (ec) throw_duration_error(); + return to; +#endif +} + +template ::value&& + std::is_floating_point::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) throw_duration_error(); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t(sys_time time_point) -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return detail::duration_cast>( + time_point.time_since_epoch()) + .count(); +} + +// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without +// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160. +template FMT_CONSTEXPR auto has_current_zone() -> bool { + using namespace std::chrono; + using namespace fmt_detail; + return !std::is_same::value; +} +} // namespace detail + +FMT_BEGIN_EXPORT + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in local time. Unlike `std::localtime`, this function is + * thread-safe on most platforms. + */ +inline auto localtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + inline dispatcher(std::time_t t) : time_(t) {} + + inline auto run() -> bool { + using namespace fmt::detail; + return handle(localtime_r(&time_, &tm_)); + } + + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + inline auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(localtime_s(&tm_, &time_)); + } + + inline auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + inline auto fallback(detail::null<>) -> bool { + using namespace fmt::detail; + std::tm* tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + dispatcher lt(time); + // Too big time values may be unsupported. + if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); + return lt.tm_; +} + +#if FMT_USE_LOCAL_TIME +template ())> +inline auto localtime(std::chrono::local_time time) -> std::tm { + using namespace std::chrono; + using namespace fmt_detail; + return localtime(detail::to_time_t(current_zone()->to_sys(time))); +} +#endif + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this + * function is thread-safe on most platforms. + */ +inline auto gmtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + inline dispatcher(std::time_t t) : time_(t) {} + + inline auto run() -> bool { + using namespace fmt::detail; + return handle(gmtime_r(&time_, &tm_)); + } + + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + inline auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(gmtime_s(&tm_, &time_)); + } + + inline auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + inline auto fallback(detail::null<>) -> bool { + std::tm* tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + auto gt = dispatcher(time); + // Too big time values may be unsupported. + if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); + return gt.tm_; +} + +template +inline auto gmtime(sys_time time_point) -> std::tm { + return gmtime(detail::to_time_t(time_point)); +} + +namespace detail { + +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); + } +} + +template +FMT_CONSTEXPR inline auto get_units() -> const char* { + if (std::is_same::value) return "as"; + if (std::is_same::value) return "fs"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) + return detail::use_utf8 ? "µs" : "us"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "min"; + if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; + return nullptr; +} + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Glibc extensions for formatting numeric values. +enum class pad_type { + // Pad a numeric result string with zeros (the default). + zero, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + +// Parses a put_time-like format string and invokes handler actions. +template +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); + auto ptr = begin; + while (ptr != end) { + pad_type pad = pad_type::zero; + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case '%': handler.on_text(ptr - 1, ptr); break; + case 'n': { + const Char newline[] = {'\n'}; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const Char tab[] = {'\t'}; + handler.on_text(tab, tab + 1); + break; + } + // Year: + case 'Y': handler.on_year(numeric_system::standard, pad); break; + case 'y': handler.on_short_year(numeric_system::standard); break; + case 'C': handler.on_century(numeric_system::standard); break; + case 'G': handler.on_iso_week_based_year(); break; + case 'g': handler.on_iso_week_based_short_year(); break; + // Day of the week: + case 'a': handler.on_abbr_weekday(); break; + case 'A': handler.on_full_weekday(); break; + case 'w': handler.on_dec0_weekday(numeric_system::standard); break; + case 'u': handler.on_dec1_weekday(numeric_system::standard); break; + // Month: + case 'b': + case 'h': handler.on_abbr_month(); break; + case 'B': handler.on_full_month(); break; + case 'm': handler.on_dec_month(numeric_system::standard, pad); break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard, pad); + break; + case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break; + case 'j': handler.on_day_of_year(pad); break; + case 'd': handler.on_day_of_month(numeric_system::standard, pad); break; + case 'e': + handler.on_day_of_month(numeric_system::standard, pad_type::space); + break; + // Hour, minute, second: + case 'H': handler.on_24_hour(numeric_system::standard, pad); break; + case 'I': handler.on_12_hour(numeric_system::standard, pad); break; + case 'M': handler.on_minute(numeric_system::standard, pad); break; + case 'S': handler.on_second(numeric_system::standard, pad); break; + // Other: + case 'c': handler.on_datetime(numeric_system::standard); break; + case 'x': handler.on_loc_date(numeric_system::standard); break; + case 'X': handler.on_loc_time(numeric_system::standard); break; + case 'D': handler.on_us_date(); break; + case 'F': handler.on_iso_date(); break; + case 'r': handler.on_12_hour_time(); break; + case 'R': handler.on_24_hour_time(); break; + case 'T': handler.on_iso_time(); break; + case 'p': handler.on_am_pm(); break; + case 'Q': handler.on_duration_value(); break; + case 'q': handler.on_duration_unit(); break; + case 'z': handler.on_utc_offset(numeric_system::standard); break; + case 'Z': handler.on_tz_name(); break; + // Alternative representation: + case 'E': { + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'Y': handler.on_year(numeric_system::alternative, pad); break; + case 'y': handler.on_offset_year(); break; + case 'C': handler.on_century(numeric_system::alternative); break; + case 'c': handler.on_datetime(numeric_system::alternative); break; + case 'x': handler.on_loc_date(numeric_system::alternative); break; + case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + } + case 'O': + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'y': handler.on_short_year(numeric_system::alternative); break; + case 'm': handler.on_dec_month(numeric_system::alternative, pad); break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative, pad); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::alternative, pad_type::space); + break; + case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; + case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; + case 'H': handler.on_24_hour(numeric_system::alternative, pad); break; + case 'I': handler.on_12_hour(numeric_system::alternative, pad); break; + case 'M': handler.on_minute(numeric_system::alternative, pad); break; + case 'S': handler.on_second(numeric_system::alternative, pad); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + default: FMT_THROW(format_error("invalid format")); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; +} + +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->unsupported(); + } + FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } + FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } + FMT_CONSTEXPR void on_full_weekday() { unsupported(); } + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_abbr_month() { unsupported(); } + FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_us_date() { unsupported(); } + FMT_CONSTEXPR void on_iso_date() { unsupported(); } + FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_iso_time() { unsupported(); } + FMT_CONSTEXPR void on_am_pm() { unsupported(); } + FMT_CONSTEXPR void on_duration_value() { unsupported(); } + FMT_CONSTEXPR void on_duration_unit() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } +}; + +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN inline void unsupported() { + FMT_THROW(format_error("no format")); + } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_tz_name() {} +}; + +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +inline void tzset_once() { + static bool init = []() { + using namespace fmt_detail; + _tzset(); + return false; + }(); + ignore_unused(init); +} + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return int_value; +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, pow10(num_fractional_digits)>>; + + const auto fractional = d - detail::duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : detail::duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits); + } + } else if (precision > 0) { + *out++ = '.'; + leading_zeroes = min_of(leading_zeroes, precision); + int remaining = precision - leading_zeroes; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(pow10(to_unsigned(num_truncated_digits))); + if (n != 0) out = format_decimal(out, n, remaining); + return; + } + if (n != 0) { + out = format_decimal(out, n, num_digits); + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year, pad_type pad) { + // At least 4 characters. + int width = 4; + bool negative = year < 0; + if (negative) { + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (negative && pad == pad_type::zero) *out_++ = '-'; + if (width > num_digits) { + out_ = detail::write_padding(out_, pad, width - num_digits); + } + if (negative && pad != pad_type::zero) *out_++ = '-'; + out_ = format_decimal(out_, n, num_digits); + } + void write_year(long long year, pad_type pad) { + write_year_extended(year, pad); + } + + void write_utc_offset(long long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { +#if defined(_WIN32) && defined(_UCRT) + tzset_once(); + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset, ns); +#else + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long long offset = gt - lt; + write_utc_offset(offset, ns); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month(numeric_system::standard, pad_type::space); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard, pad_type::space); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + write2digits(buf, static_cast(year / 100)); + } else { + offset = 4; + write_year_extended(year, pad_type::zero); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year(), pad); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1, pad); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week, + pad); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year(), pad); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { + write_year(tm_iso_week_year(), pad_type::zero); + } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year(pad_type pad) { + auto yday = tm_yday() + 1; + auto digit1 = yday / 100; + if (digit1 != 0) { + write1(digit1); + } else { + out_ = detail::write_padding(out_, pad); + } + write2(yday % 100, pad); + } + + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) + FMT_THROW(format_error("precision not allowed for this argument type")); + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; +} + +template ::value)> +inline auto mod(T x, int y) -> T { + return x % static_cast(y); +} +template ::value)> +inline auto mod(T x, int y) -> T { + return std::fmod(x, static_cast(y)); +} + +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template ::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template struct make_unsigned_or_unchanged { + using type = typename std::make_unsigned::type; +}; + +template ::value)> +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { + // this may overflow and/or the result may not fit in the + // target type. +#if FMT_SAFE_DURATION_CAST + using CommonSecondsType = + typename std::common_type::type; + const auto d_as_common = detail::duration_cast(d); + const auto d_as_whole_seconds = + detail::duration_cast(d_as_common); + // this conversion should be nonproblematic + const auto diff = d_as_common - d_as_whole_seconds; + const auto ms = + detail::duration_cast>(diff); + return ms; +#else + auto s = detail::duration_cast(d); + return detail::duration_cast(d - s); +#endif +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { + return write(out, val); +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); + specs.precision = precision; + specs.set_type(precision >= 0 ? presentation_type::fixed + : presentation_type::general); + return write(out, val, specs); +} + +template +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { + return copy(unit.begin(), unit.end(), out); +} + +template +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return copy(u.c_str(), u.c_str() + u.size(), out); +} + +template +auto format_duration_unit(OutputIt out) -> OutputIt { + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; +} + +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + if (localized) + ::new (&locale_) std::locale(loc.template get()); + } + inline ~get_locale() { + if (has_locale_) locale_.~locale(); + } + inline operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + +template +struct chrono_formatter { + FormatContext& context; + OutputIt out; + int precision; + bool localized = false; + // rep is unsigned to avoid overflow. + using rep = + conditional_t::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged::type>; + rep val; + using seconds = std::chrono::duration; + seconds s; + using milliseconds = std::chrono::duration; + bool negative; + + using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; + + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) + : context(ctx), + out(o), + val(static_cast(d.count())), + negative(false) { + if (d.count() < 0) { + val = 0 - val; + negative = true; + } + + // this may overflow and/or the result may not fit in the + // target type. + // might need checked conversion (rep!=Rep) + s = detail::duration_cast(std::chrono::duration(val)); + } + + // returns true if nan or inf, writes to out. + auto handle_nan_inf() -> bool { + if (isfinite(val)) { + return false; + } + if (isnan(val)) { + write_nan(); + return true; + } + // must be +-inf + if (val > 0) { + write_pinf(); + } else { + write_ninf(); + } + return true; + } + + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } + + auto hour12() const -> Rep { + Rep hour = static_cast(mod((s.count() / 3600), 12)); + return hour <= 0 ? 12 : hour; + } + + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } + + auto time() const -> std::tm { + auto time = std::tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + time.tm_min = to_nonnegative_int(minute(), 60); + time.tm_sec = to_nonnegative_int(second(), 60); + return time; + } + + void write_sign() { + if (negative) { + *out++ = '-'; + negative = false; + } + } + + void write(Rep value, int width, pad_type pad = pad_type::zero) { + write_sign(); + if (isnan(value)) return write_nan(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(value, max_value())); + int num_digits = detail::count_digits(n); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } + out = format_decimal(out, n, num_digits); + } + + void write_nan() { std::copy_n("nan", 3, out); } + void write_pinf() { std::copy_n("inf", 3, out); } + void write_ninf() { std::copy_n("-inf", 4, out); } + + template + void format_tm(const tm& time, Callback cb, Args... args) { + if (isnan(val)) return write_nan(); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); + } + + void on_text(const char_type* begin, const char_type* end) { + copy(begin, end, out); + } + + // These are not implemented because durations don't have date information. + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset(numeric_system) {} + void on_tz_name() {} + void on_year(numeric_system, pad_type) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system, pad_type) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year(pad_type) { + if (handle_nan_inf()) return; + write(days(), 0); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); + } + + void on_12_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour12(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour12(), 12); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); + } + + void on_minute(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(minute(), 2, pad); + auto time = tm(); + time.tm_min = to_nonnegative_int(minute(), 60); + format_tm(time, &tm_writer_type::on_minute, ns, pad); + } + + void on_second(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } + out = copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); + } + return; + } + auto time = tm(); + time.tm_sec = to_nonnegative_int(second(), 60); + format_tm(time, &tm_writer_type::on_second, ns, pad); + } + + void on_12_hour_time() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_12_hour_time); + } + + void on_24_hour_time() { + if (handle_nan_inf()) { + *out++ = ':'; + handle_nan_inf(); + return; + } + + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + if (handle_nan_inf()) return; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_am_pm); + } + + void on_duration_value() { + if (handle_nan_inf()) return; + write_sign(); + out = format_duration_value(out, val, precision); + } + + void on_duration_unit() { + out = format_duration_unit(out); + } +}; + +} // namespace detail + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; +#else +// A fallback version of weekday. +class weekday { + private: + unsigned char value_; + + public: + weekday() = default; + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } +}; + +class day { + private: + unsigned char value_; + + public: + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class month { + private: + unsigned char value_; + + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } +}; + +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; +#endif + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_wday = static_cast(wd.c_encoding()); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + bool localized_ = false; + basic_string_view fmt_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); + } + if (it != end && *it == 'L') { + localized_ = true; + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(std::chrono::duration d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = fmt_.begin(), end = fmt_.end(); + // As a possible future optimization, we could avoid extra copying if width + // is not specified. + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), precision, + precision_ref_, ctx); + if (begin == end || *begin == '}') { + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); + } else { + using chrono_formatter = + detail::chrono_formatter; + auto f = chrono_formatter(ctx, out, d); + f.precision = precision; + f.localized = localized_; + detail::parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + protected: + basic_string_view fmt_; + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format string only if the new spec is not empty. + if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(sys_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = detail::duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) + --tm.tm_sec; + else + tm = gmtime(val - second); + subsecs += detail::duration_cast(std::chrono::seconds(1)); + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +template +struct formatter, Char> + : formatter, Char> { + template + auto format(utc_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format( + detail::utc_clock::to_sys(val), ctx); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num == 1 && period::den == 1 && + !std::is_floating_point::value) { + return formatter::format(localtime(val), ctx); + } + auto epoch = val.time_since_epoch(); + auto subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + return formatter::do_format(localtime(val), ctx, &subsecs); + } +}; + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/color.h b/libs/openal-soft/fmt-11.1.1/include/fmt/color.h new file mode 100644 index 00000000..2faaf3a0 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/color.h @@ -0,0 +1,610 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} + FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + FMT_CONSTEXPR rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + FMT_CONSTEXPR rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace detail { + +// color is a struct of either a rgb color or a terminal color. +struct color_type { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = static_cast(rgb_color); + } + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { + value.rgb_color = (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b; + } + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { + value.term_color = static_cast(term_color); + } + bool is_rgb; + union color_union { + uint8_t term_color; + uint32_t rgb_color; + } value; +}; +} // namespace detail + +/// A text style consisting of foreground and background colors and emphasis. +class text_style { + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} + + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + report_error("can't OR a terminal color"); + foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + report_error("can't OR a terminal color"); + background_color.value.rgb_color |= rhs.background_color.value.rgb_color; + } + + ems = static_cast(static_cast(ems) | + static_cast(rhs.ems)); + return *this; + } + + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { + return lhs |= rhs; + } + + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { + return set_foreground_color; + } + FMT_CONSTEXPR auto has_background() const noexcept -> bool { + return set_background_color; + } + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { + return static_cast(ems) != 0; + } + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return foreground_color; + } + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { + FMT_ASSERT(has_background(), "no background specified for this style"); + return background_color; + } + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return ems; + } + + private: + FMT_CONSTEXPR text_style(bool is_foreground, + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { + if (is_foreground) { + foreground_color = text_color; + set_foreground_color = true; + } else { + background_color = text_color; + set_background_color = true; + } + } + + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; + + detail::color_type foreground_color; + detail::color_type background_color; + bool set_foreground_color; + bool set_background_color; + emphasis ems; +}; + +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return text_style(true, foreground); +} + +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return text_style(false, background); +} + +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { + return text_style(lhs) | rhs; +} + +namespace detail { + +template struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(color_type text_color, + const char* esc) noexcept { + // If we have a terminal color, we need to output another escape code + // sequence. + if (!text_color.is_rgb) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value.term_color; + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + size_t index = 0; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + + if (value >= 100u) { + buffer[index++] = static_cast('1'); + value %= 100u; + } + buffer[index++] = static_cast('0' + value / 10u); + buffer[index++] = static_cast('0' + value % 10u); + + buffer[index++] = static_cast('m'); + buffer[index++] = static_cast('\0'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast(esc[i]); + } + rgb color(text_color.value.rgb_color); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + buffer[19] = static_cast(0); + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; + + size_t index = 0; + for (size_t i = 0; i < num_emphases; ++i) { + if (!em_codes[i]) continue; + buffer[index++] = static_cast('\x1b'); + buffer[index++] = static_cast('['); + buffer[index++] = static_cast('0' + em_codes[i]); + buffer[index++] = static_cast('m'); + } + buffer[index++] = static_cast(0); + } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } + + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); + } + + private: + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) noexcept { + out[0] = static_cast('0' + c / 100); + out[1] = static_cast('0' + c / 10 % 10); + out[2] = static_cast('0' + c % 10); + out[3] = static_cast(delimiter); + } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } +}; + +template +FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); +} + +template +FMT_CONSTEXPR auto make_background_color(color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); +} + +template +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { + return ansi_color_escape(em); +} + +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template struct styled_arg : view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + +template +void vformat_to(buffer& buf, const text_style& ts, + basic_string_view fmt, + basic_format_args> args) { + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + has_style = true; + auto background = make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + vformat_to(buf, fmt, args); + if (has_style) reset_color(buf); +} +} // namespace detail + +inline void vprint(FILE* f, const text_style& ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); +} + +/** + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt.str, vargs{{args...}}); +} + +/** + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); +} + +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return fmt::to_string(buf); +} + +/** + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt.str, vargs{{args...}}); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); +} + +/** + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt.str, vargs{{args...}}); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = detail::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = detail::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = detail::copy(background.begin(), background.end(), out); + } + out = formatter::format(arg.value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = detail::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/compile.h b/libs/openal-soft/fmt-11.1.1/include/fmt/compile.h new file mode 100644 index 00000000..68b451c7 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/compile.h @@ -0,0 +1,551 @@ +// Formatting library for C++ - experimental format string compilation +// +// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COMPILE_H_ +#define FMT_COMPILE_H_ + +#ifndef FMT_MODULE +# include // std::back_inserter +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// A compile-time string which is compiled into fast formatting code. +FMT_EXPORT class compiled_string {}; + +namespace detail { + +template +struct is_compiled_string : std::is_base_of {}; + +/** + * Converts a string literal `s` into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires C++17 + * `constexpr if` compiler support. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); + */ +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) +#else +# define FMT_COMPILE(s) FMT_STRING(s) +#endif + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct udl_compiled_string : compiled_string { + using char_type = Char; + constexpr explicit operator basic_string_view() const { + return {Str.data, N - 1}; + } +}; +#endif + +template +auto first(const T& value, const Tail&...) -> const T& { + return value; +} + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +template struct type_list {}; + +// Returns a reference to the argument at index N from [first, rest...]. +template +constexpr const auto& get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) { + static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); + if constexpr (N == 0) + return first; + else + return detail::get(rest...); +} + +# if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_static_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +# endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +# if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +# endif + (void)name; + return -1; +} + +template +constexpr int get_arg_index_by_name(basic_string_view name, + type_list) { + return get_arg_index_by_name(name); +} + +template struct get_type_impl; + +template struct get_type_impl> { + using type = + remove_cvref_t(std::declval()...))>; +}; + +template +using get_type = typename get_type_impl::type; + +template struct is_compiled_format : std::false_type {}; + +template struct text { + basic_string_view data; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + return write(out, data); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr text make_text(basic_string_view s, size_t pos, + size_t size) { + return {{&s[pos], size}}; +} + +template struct code_unit { + Char value; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + *out++ = value; + return out; + } +}; + +// This ensures that the argument type is convertible to `const T&`. +template +constexpr const T& get_arg_checked(const Args&... args) { + const auto& arg = detail::get(args...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N. +template struct field { + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + const T& arg = get_arg_checked(args...); + if constexpr (std::is_convertible>::value) { + auto s = basic_string_view(arg); + return copy(s.begin(), s.end(), out); + } else { + return write(out, arg); + } + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument with name. +template struct runtime_named_field { + using char_type = Char; + basic_string_view name; + + template + constexpr static bool try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view arg_name, const T& arg) { + if constexpr (is_named_arg::type>::value) { + if (arg_name == arg.name) { + out = write(out, arg.value); + return true; + } + } + return false; + } + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + bool found = (try_format_argument(out, name, args) || ...); + if (!found) { + FMT_THROW(format_error("argument with specified name is not found")); + } + return out; + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N and has format specifiers. +template struct spec_field { + using char_type = Char; + formatter fmt; + + template + constexpr FMT_INLINE OutputIt format(OutputIt out, + const Args&... args) const { + const auto& vargs = + fmt::make_format_args>(args...); + basic_format_context ctx(out, vargs); + return fmt.format(get_arg_checked(args...), ctx); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template struct concat { + L lhs; + R rhs; + using char_type = typename L::char_type; + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + out = lhs.format(out, args...); + return rhs.format(out, args...); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr concat make_concat(L lhs, R rhs) { + return {lhs, rhs}; +} + +struct unknown_format {}; + +template +constexpr size_t parse_text(basic_string_view str, size_t pos) { + for (size_t size = str.size(); pos != size; ++pos) { + if (str[pos] == '{' || str[pos] == '}') break; + } + return pos; +} + +template +constexpr auto compile_format_string(S fmt); + +template +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); + if constexpr (std::is_same, + unknown_format>()) + return tail; + else + return make_concat(head, tail); + } else { + return head; + } +} + +template struct parse_specs_result { + formatter fmt; + size_t end; + int next_arg_id; +}; + +enum { manual_indexing_id = -1 }; + +template +constexpr parse_specs_result parse_specs(basic_string_view str, + size_t pos, int next_arg_id) { + str.remove_prefix(pos); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); + auto f = formatter(); + auto end = f.parse(ctx); + return {f, pos + fmt::detail::to_unsigned(end - str.data()), + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template struct arg_id_handler { + arg_id_kind kind; + arg_ref arg_id; + + constexpr int on_auto() { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + constexpr int on_index(int id) { + kind = arg_id_kind::index; + arg_id = arg_ref(id); + return 0; + } + constexpr int on_name(basic_string_view id) { + kind = arg_id_kind::name; + arg_id = arg_ref(id); + return 0; + } +}; + +template struct parse_arg_id_result { + arg_id_kind kind; + arg_ref arg_id; + const Char* arg_id_end; +}; + +template +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; + auto arg_id_end = parse_arg_id(begin, end, handler); + return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; +} + +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), fmt); + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + fmt); + } + } +} + +// Compiles a non-empty format string and returns the compiled representation +// or unknown_format() on unrecognized input. +template +constexpr auto compile_format_string(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + if constexpr (str[POS] == '{') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '{' in format string")); + if constexpr (str[POS + 1] == '{') { + return parse_tail(make_text(str, POS, 1), fmt); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>(fmt); + } else { + constexpr auto arg_id_result = + parse_arg_id(str.data() + POS + 1, str.data() + str.size()); + constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); + constexpr char_type c = + arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); + static_assert(c == '}' || c == ':', "missing '}' in format string"); + if constexpr (arg_id_result.kind == arg_id_kind::index) { + static_assert( + ID == manual_indexing_id || ID == 0, + "cannot switch from automatic to manual argument indexing"); + constexpr auto arg_index = arg_id_result.arg_id.index; + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + fmt); + } else if constexpr (arg_id_result.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); + if constexpr (arg_index >= 0) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(fmt); + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.name}, fmt); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } + } + } else if constexpr (str[POS] == '}') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '}' in format string")); + return parse_tail(make_text(str, POS, 1), fmt); + } else { + constexpr auto end = parse_text(str, POS + 1); + if constexpr (end - POS > 1) { + return parse_tail(make_text(str, POS, end - POS), fmt); + } else { + return parse_tail(code_unit{str[POS]}, fmt); + } + } +} + +template ::value)> +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); + if constexpr (str.size() == 0) { + return detail::make_text(str, 0, 0); + } else { + constexpr auto result = + detail::compile_format_string, 0, 0>(fmt); + return result; + } +} +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +} // namespace detail + +FMT_BEGIN_EXPORT + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) + +template ::value)> +FMT_INLINE std::basic_string format(const CompiledFormat& cf, + const Args&... args) { + auto s = std::basic_string(); + cf.format(std::back_inserter(s), args...); + return s; +} + +template ::value)> +constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, + const Args&... args) { + return cf.format(out, args...); +} + +template ::value)> +FMT_INLINE std::basic_string format(const S&, + Args&&... args) { + if constexpr (std::is_same::value) { + constexpr auto str = basic_string_view(S()); + if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { + const auto& first = detail::first(args...); + if constexpr (detail::is_named_arg< + remove_cvref_t>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format( + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format(compiled, std::forward(args)...); + } +} + +template ::value)> +FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_to( + out, static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_to(out, compiled, std::forward(args)...); + } +} +#endif + +template ::value)> +auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); + return {buf.out(), buf.count()}; +} + +template ::value)> +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) + -> size_t { + auto buf = detail::counting_buffer<>(); + fmt::format_to(appender(buf), fmt, args...); + return buf.count(); +} + +template ::value)> +void print(std::FILE* f, const S& fmt, const Args&... args) { + auto buf = memory_buffer(); + fmt::format_to(appender(buf), fmt, args...); + detail::print(f, {buf.data(), buf.size()}); +} + +template ::value)> +void print(const S& fmt, const Args&... args) { + print(stdout, fmt, args...); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +template constexpr auto operator""_cf() { + using char_t = remove_cvref_t; + return detail::udl_compiled_string(); +} +} // namespace literals +#endif + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COMPILE_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/core.h b/libs/openal-soft/fmt-11.1.1/include/fmt/core.h new file mode 100644 index 00000000..8ca735f0 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/core.h @@ -0,0 +1,5 @@ +// 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" diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/format-inl.h b/libs/openal-soft/fmt-11.1.1/include/fmt/format-inl.h new file mode 100644 index 00000000..38308bf2 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/format-inl.h @@ -0,0 +1,1947 @@ +// Formatting library for C++ - implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#ifndef FMT_MODULE +# include +# include // errno +# include +# include +# include +#endif + +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) +# include // _isatty +#endif + +#include "format.h" + +#if FMT_USE_LOCALE +# include +#endif + +#ifndef FMT_FUNC +# define FMT_FUNC +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +FMT_FUNC void assert_fail(const char* file, int line, const char* message) { + // Use unchecked std::fprintf to avoid triggering another assertion when + // writing to stderr fails. + fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + abort(); +} + +FMT_FUNC void format_error_code(detail::buffer& out, int error_code, + string_view message) noexcept { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.try_resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + auto abs_value = static_cast>(error_code); + if (detail::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = appender(out); + if (message.size() <= inline_buffer_size - error_code_size) + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); +} + +FMT_FUNC void do_report_error(format_func func, int error_code, + const char* message) noexcept { + memory_buffer full_message; + func(full_message, error_code, message); + // Don't use fwrite_all because the latter may throw. + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); +} + +// A wrapper around fwrite that throws on error. +inline void fwrite_all(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); + if (written < count) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +#if FMT_USE_LOCALE +using std::locale; +using std::numpunct; +using std::use_facet; + +template > +locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { + static_assert(std::is_same::value, ""); +} +#else +struct locale {}; +template struct numpunct { + auto grouping() const -> std::string { return "\03"; } + auto thousands_sep() const -> Char { return ','; } + auto decimal_point() const -> Char { return '.'; } +}; +template Facet use_facet(locale) { return {}; } +#endif // FMT_USE_LOCALE + +template auto locale_ref::get() const -> Locale { + static_assert(std::is_same::value, ""); +#if FMT_USE_LOCALE + if (locale_) return *static_cast(locale_); +#endif + return locale(); +} + +template +FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { + auto&& facet = use_facet>(loc.get()); + auto grouping = facet.grouping(); + auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); + return {std::move(grouping), thousands_sep}; +} +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { + return use_facet>(loc.get()).decimal_point(); +} + +#if FMT_USE_LOCALE +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +} +#endif +} // namespace detail + +FMT_FUNC void report_error(const char* message) { +#if FMT_USE_EXCEPTIONS + throw format_error(message); +#else + fputs(message, stderr); + abort(); +#endif +} + +template typename Locale::id format_facet::id; + +template format_facet::format_facet(Locale& loc) { + auto& np = detail::use_facet>(loc); + grouping_ = np.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); +} + +#if FMT_USE_LOCALE +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} +#endif + +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(fmt, args)); +} + +namespace detail { + +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { + return x.f == y.f && x.e == y.e; +} + +// Compilers should be able to optimize this into the ror instruction. +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { + r &= 31; + return (n >> r) | (n << (32 - r)); +} +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { + r &= 63; + return (n >> r) | (n << (64 - r)); +} + +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return umul128_upper64(static_cast(x) << 32, y); +} + +// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint64_t high = x * y.high(); + uint128_fallback high_low = umul128(x, y.low()); + return {high + high_low.high(), high_low.low()}; +} + +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return x * y; +} + +// Various fast log computations. +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { + FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); + return (e * 631305 - 261663) >> 21; +} + +FMT_INLINE_VARIABLE constexpr struct { + uint32_t divisor; + int shift_amount; +} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; + +// Replaces n by floor(n / pow(10, N)) returning true if and only if n is +// divisible by pow(10, N). +// Precondition: n <= pow(10, N + 1). +template +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { + // The numbers below are chosen such that: + // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, + // 2. nm mod 2^k < m if and only if n is divisible by d, + // where m is magic_number, k is shift_amount + // and d is divisor. + // + // Item 1 is a common technique of replacing division by a constant with + // multiplication, see e.g. "Division by Invariant Integers Using + // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set + // to ceil(2^k/d) for large enough k. + // The idea for item 2 originates from Schubfach. + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + n *= magic_number; + const uint32_t comparison_mask = (1u << info.shift_amount) - 1; + bool result = (n & comparison_mask) < magic_number; + n >>= info.shift_amount; + return result; +} + +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + return (n * magic_number) >> info.shift_amount; +} + +// Computes floor(n / 10^(kappa + 1)) (float) +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { + // 1374389535 = ceil(2^37/100) + return static_cast((static_cast(n) * 1374389535) >> 37); +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { + // 2361183241434822607 = ceil(2^(64+7)/1000) + return umul128_upper64(n, 2361183241434822607ull) >> 7; +} + +// Various subroutines using pow10 cache +template struct cache_accessor; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint64_t; + + static auto get_cached_power(int k) noexcept -> uint64_t { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + static constexpr const uint64_t pow10_significands[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, + 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, + 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, + 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, + 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, + 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; + return pow10_significands[k - float_info::min_k]; + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul96_upper64(u, cache); + return {static_cast(r >> 32), + static_cast(r) == 0}; + } + + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul96_lower64(two_f, cache); + return {((r >> (64 - beta)) & 1) != 0, + static_cast(r >> (32 - beta)) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache - (cache >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache + (cache >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (static_cast( + cache >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint128_fallback; + + static auto get_cached_power(int k) noexcept -> uint128_fallback { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + + static constexpr const uint128_fallback pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, +#else + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} +#endif + }; + +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info::min_k]; +#else + static constexpr const uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info::min_k; + int offset = k - kb; + + // Get base cache. + uint128_fallback base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); + uint128_fallback middle_low = umul128(base_cache.low(), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); + return {recovered_cache.high(), recovered_cache.low() + 1}; +#endif + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul192_upper128(u, cache); + return {r.high(), r.low() == 0}; + } + + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache.high() >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul192_lower128(two_f, cache); + return {((r.high() >> (64 - beta)) & 1) != 0, + ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() - + (cache.high() >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() + + (cache.high() >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { + return cache_accessor::get_cached_power(k); +} + +// Various integer checks +template +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { + const int case_shorter_interval_left_endpoint_lower_threshold = 2; + const int case_shorter_interval_left_endpoint_upper_threshold = 3; + return exponent >= case_shorter_interval_left_endpoint_lower_threshold && + exponent <= case_shorter_interval_left_endpoint_upper_threshold; +} + +// Remove trailing zeros from n and return the number of zeros removed (float) +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { + FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + return s; +} + +// Removes trailing zeros and returns the number of zeros removed (double) +FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { + FMT_ASSERT(n != 0, ""); + + // This magic number is ceil(2^90 / 10^8). + constexpr uint64_t magic_number = 12379400392853802749ull; + auto nm = umul128(n, magic_number); + + // Is n is divisible by 10^8? + if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { + // If yes, work with the quotient... + auto n32 = static_cast(nm.high() >> (90 - 64)); + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); + n = n32; + return s; + } + + // If n is not divisible by 10^8, work with n itself. + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 + + int s = 0; + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + + return s; +} + +// The main algorithm for shorter interval case +template +FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { + decimal_fp ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor::cache_entry_type; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + + auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( + cache, beta); + auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( + cache, beta); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + } + + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor::compute_round_up_for_shorter_interval_case(cache, + beta); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info::shorter_interval_tie_lower_threshold && + exponent <= float_info::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; + } + return ret_value; +} + +template auto to_decimal(T x) noexcept -> decimal_fp { + // Step 1: integer promotion & Schubfach multiplier calculation. + + using carrier_uint = typename float_info::carrier_uint; + using cache_entry_type = typename cache_accessor::cache_entry_type; + auto br = bit_cast(x); + + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = + static_cast((br & exponent_mask()) >> num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + + // Shorter interval case; proceed like Schubfach. + // In fact, when exponent == 1 and significand == 0, the interval is + // regular. However, it can be shown that the end-results are anyway same. + if (significand == 0) return shorter_interval_case(exponent); + + significand |= (static_cast(1) << num_significand_bits()); + } else { + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = + std::numeric_limits::min_exponent - num_significand_bits() - 1; + } + + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai. + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor::compute_delta(cache, beta); + const carrier_uint two_fc = significand << 1; + + // For the case of binary32, the result of integer check is not correct for + // 29711844 * 2^-82 + // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + // and 29711844 * 2^-81 + // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + // and they are the unique counterexamples. However, since 29711844 is even, + // this does not cause any problem for the endpoints calculations; it can only + // cause a problem when we need to perform integer check for the center. + // Fortunately, with these inputs, that branch is never executed, so we are + // fine. + const typename cache_accessor::compute_mul_result z_mul = + cache_accessor::compute_mul((two_fc | 1) << beta, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary. + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here. + decimal_fp ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); + uint32_t r = static_cast(z_mul.result - float_info::big_divisor * + ret_value.significand); + + if (r < deltai) { + // Exclude the right endpoint if necessary. + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { + --ret_value.significand; + r = float_info::big_divisor; + goto small_divisor_case_label; + } + } else if (r > deltai) { + goto small_divisor_case_label; + } else { + // r == deltai; compare fractional parts. + const typename cache_accessor::compute_mul_parity_result x_mul = + cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); + + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) + goto small_divisor_case_label; + } + ret_value.exponent = minus_k + float_info::kappa + 1; + + // We may need to remove trailing zeros. + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor. + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info::kappa; + + uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + + // Is dist divisible by 10^kappa? + const bool divisible_by_small_divisor = + check_divisibility_and_divide_by_pow10::kappa>(dist); + + // Add dist / 10^kappa to the significand. + ret_value.significand += dist; + + if (!divisible_by_small_divisor) return ret_value; + + // Check z^(f) >= epsilon^(f). + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number. + const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); + + // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), + // or equivalently, when y is an integer. + if (y_mul.parity != approx_y_parity) + --ret_value.significand; + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) + --ret_value.significand; + return ret_value; +} +} // namespace dragonbox +} // namespace detail + +template <> struct formatter { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { + return ctx.begin(); + } + + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { + auto out = ctx.out(); + bool first = true; + for (auto i = n.bigits_.size(); i > 0; --i) { + auto value = n.bigits_[i - 1u]; + if (first) { + out = fmt::format_to(out, FMT_STRING("{:x}"), value); + first = false; + continue; + } + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); + } + if (n.exp_ > 0) + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); + return out; + } +}; + +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { + for_each_codepoint(s, [this](uint32_t cp, string_view) { + if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); + if (cp <= 0xFFFF) { + buffer_.push_back(static_cast(cp)); + } else { + cp -= 0x10000; + buffer_.push_back(static_cast(0xD800 + (cp >> 10))); + buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); + } + return true; + }); + buffer_.push_back(0); +} + +FMT_FUNC void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept { + FMT_TRY { + auto ec = std::error_code(error_code, std::generic_category()); + detail::write(appender(out), std::system_error(ec, message).what()); + return; + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +FMT_FUNC void report_system_error(int error_code, + const char* message) noexcept { + do_report_error(format_system_error, error_code, message); +} + +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { + // Don't optimize the "{}" case to keep the binary size small and because it + // can be better optimized in fmt::format anyway. + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + return to_string(buffer); +} + +namespace detail { + +FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc) { + auto out = appender(buf); + if (fmt.size() == 2 && equal2(fmt.data(), "{}")) + return args.get(0).visit(default_arg_formatter{out}); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} + +template struct span { + T* data; + size_t size; +}; + +template auto flockfile(F* f) -> decltype(_lock_file(f)) { + _lock_file(f); +} +template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { + _unlock_file(f); +} + +#ifndef getc_unlocked +template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { + return _fgetc_nolock(f); +} +#endif + +template +struct has_flockfile : std::false_type {}; + +template +struct has_flockfile()))>> + : std::true_type {}; + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } + + void flush() { fflush(this->file_); } +}; + +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_IO_write_ptr) return; + // Force buffer initialization by placing and removing a char in a buffer. + assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end); + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; + } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +#ifndef FMT_USE_FALLBACK_FILE +# define FMT_USE_FALLBACK_FILE 0 +#endif + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} + +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +template +class file_print_buffer : public buffer { + public: + explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} +}; + +template +class file_print_buffer::value>> + : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + F* f = file_; // Make funlockfile depend on the template parameter F + funlockfile(f); // for the system API detection to work. + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +#else +using dword = conditional_t; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); + +FMT_FUNC bool write_console(int fd, string_view text) { + auto u16 = utf8_to_utf16(text); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), nullptr, nullptr) != 0; +} +#endif + +#ifdef _WIN32 +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_all(buffer.data(), buffer.size(), f); +} +#endif + +FMT_FUNC void print(std::FILE* f, string_view text) { +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_all(text.data(), text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) + return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer<>(f); + return detail::vformat_to(buffer, fmt, args); +} + +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); +} + +namespace detail { + +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// This code is generated by support/printable.py. +FMT_FUNC auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +} // namespace detail + +FMT_END_NAMESPACE + +#endif // FMT_FORMAT_INL_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/format.h b/libs/openal-soft/fmt-11.1.1/include/fmt/format.h new file mode 100644 index 00000000..d1b83d18 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/format.h @@ -0,0 +1,4220 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES +#endif + +#include "base.h" + +#ifndef FMT_MODULE +# include // std::signbit +# include // std::byte +# include // uint32_t +# include // std::memcpy +# include // std::numeric_limits +# include // std::bad_alloc +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error + +// Check FMT_CPLUSPLUS to avoid a warning in MSVC. +# if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include // std::bit_cast +# endif + +// libc++ supports string_view in pre-c++17. +# if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# include +# define FMT_USE_STRING_VIEW +# endif + +# if FMT_MSC_VERSION +# include // _BitScanReverse[64], _umul128 +# endif +#endif // FMT_MODULE + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif + +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE +#endif + +// Check if RTTI is disabled. +#ifdef FMT_USE_RTTI +// Use the provided definition. +#elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# define FMT_USE_RTTI 1 +#else +# define FMT_USE_RTTI 0 +#endif + +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) +#else +# define FMT_SO_VISIBILITY(value) +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + +namespace std { +template struct iterator_traits> { + using iterator_category = output_iterator_tag; + using value_type = T; + using difference_type = + decltype(static_cast(nullptr) - static_cast(nullptr)); + using pointer = void; + using reference = void; +}; +} // namespace std + +#ifndef FMT_THROW +# if FMT_USE_EXCEPTIONS +# if FMT_MSC_VERSION || defined(__NVCC__) +FMT_BEGIN_NAMESPACE +namespace detail { +template inline void do_throw(const Exception& x) { + // Silence unreachable code warnings in MSVC and NVCC because these + // are nearly impossible to fix in a generic code. + volatile bool b = true; + if (b) throw x; +} +} // namespace detail +FMT_END_NAMESPACE +# define FMT_THROW(x) detail::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif +# else +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) +# endif // FMT_USE_EXCEPTIONS +#endif // FMT_THROW + +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + +// __builtin_clz is broken in clang with Microsoft codegen: +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC but otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# ifdef _WIN64 +# pragma intrinsic(_BitScanReverse64) +# endif +# endif + +inline auto clz(uint32_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; + _BitScanReverse(&r, x); + return 31 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZ(n) detail::clz(n) + +inline auto clzll(uint64_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + return 63 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) +#endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) + +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#else +template struct std_string_view {}; +#endif + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; +#if FMT_CPLUSPLUS < 201703L +template +constexpr Char string_literal::value[sizeof...(C)]; +#endif + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; +} + +inline auto is_big_endian() -> bool { +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else + struct bytes { + char data[sizeof(int)]; + }; + return bit_cast(1).data[0] == 0; +#endif +} + +class uint128_fallback { + private: + uint64_t lo_, hi_; + + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } + friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } + + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; + } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; + } +}; + +using uint128_t = conditional_t; + +#ifdef UINTPTR_MAX +using uintptr_t = ::uintptr_t; +#else +using uintptr_t = uint128_t; +#endif + +// Returns the largest possible value for type T. Same as +// std::numeric_limits::max() but shorter and not affected by the max macro. +template constexpr auto max_value() -> T { + return (std::numeric_limits::max)(); +} +template constexpr auto num_bits() -> int { + return std::numeric_limits::digits; +} +// std::numeric_limits::digits may return 0 for 128-bit ints. +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); + struct data_t { + unsigned short value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); +} + +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION + __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); +#endif +} + +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. +template ::value&& + is_contiguous::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +FMT_CONSTEXPR20 inline auto +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); + size_t size = c.size(); + c.resize(size + n); + return &c[size]; +} + +template +FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) + -> basic_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + return it; +} + +template +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { + return it; +} + +template +using reserve_iterator = + remove_reference_t(), 0))>; + +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template +FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} + +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { + return it; +} + +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} + +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) return fill_n(out, count, value); + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* buf_ptr, const char* ptr) { + auto cp = uint32_t(); + auto error = 0; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; + }; + + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } + } + auto num_chars_left = to_unsigned(s.data() + s.size() - p); + if (num_chars_left == 0) return; + + // Suppress bogus -Wstringop-overflow. + if (FMT_GCC_VERSION) num_chars_left &= 3; + char buf[2 * block_size - 1] = {}; + copy(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr < buf + num_chars_left); +} + +template +inline auto compute_width(basic_string_view s) -> size_t { + return s.size(); +} + +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { + size_t num_code_points = 0; + // It is not a lambda for compatibility with C++14. + struct count_code_points { + size_t* count; + FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { + *count += to_unsigned( + 1 + + (cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); + return true; + } + }; + // We could avoid branches by using utf8_decode directly. + for_each_codepoint(s, count_code_points{&num_code_points}); + return num_code_points; +} + +template +inline auto code_point_index(basic_string_view s, size_t n) -> size_t { + return min_of(n, s.size()); +} + +// Calculates the index of the nth code point in a UTF-8 string. +inline auto code_point_index(string_view s, size_t n) -> size_t { + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; +} + +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +struct float128 {}; +#endif + +template using is_float128 = std::is_same; + +template +using is_floating_point = + bool_constant::value || is_float128::value>; + +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; + +template +using is_double_double = bool_constant::digits == 106>; + +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif + +// An allocator that uses malloc/free to allow removing dependency on the C++ +// standard libary runtime. +template struct allocator { + using value_type = T; + + T* allocate(size_t n) { + FMT_ASSERT(n <= max_value() / sizeof(T), ""); + T* p = static_cast(malloc(n * sizeof(T))); + if (!p) FMT_THROW(std::bad_alloc()); + return p; + } + + void deallocate(T* p, size_t) { free(p); } +}; + +} // namespace detail + +FMT_BEGIN_EXPORT + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. + */ +template > +class basic_memory_buffer : public detail::buffer { + private: + T store_[SIZE]; + + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; + + // Deallocate memory allocated by the buffer. + FMT_CONSTEXPR20 void deallocate() { + T* data = this->data(); + if (data != store_) alloc_.deallocate(data, this->capacity()); + } + + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { + detail::abort_fuzzing_if(size > 5000); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = max_of(size, max_size); + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(buf.size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); + } + + public: + using value_type = T; + using const_reference = const T&; + + FMT_CONSTEXPR explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) + : detail::buffer(grow), alloc_(alloc) { + this->set(store_, SIZE); + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); + } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { + alloc_ = std::move(other.alloc_); + T* data = other.data(); + size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + detail::copy(other.store_, other.store_ + size, store_); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + other.clear(); + } + this->resize(size); + } + + public: + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { + move(other); + } + + /// Moves the content of the other `basic_memory_buffer` object to this one. + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { + FMT_ASSERT(this != &other, ""); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + auto get_allocator() const -> Allocator { return alloc_; } + + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. + FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } + + /// Increases the buffer capacity to `new_capacity`. + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + using detail::buffer::append; + template + FMT_CONSTEXPR20 void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } +}; + +using memory_buffer = basic_memory_buffer; + +template +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) + -> std::string { + auto size = buf.size(); + detail::assume(size < std::string().max_size()); + return {buf.data(), size}; +} + +// A writer to a buffered stream. It doesn't own the underlying stream. +class writer { + private: + detail::buffer* buf_; + + // We cannot create a file buffer in advance because any write to a FILE may + // invalidate it. + FILE* file_; + + public: + inline writer(FILE* f) : buf_(nullptr), file_(f) {} + inline writer(detail::buffer& buf) : buf_(&buf) {} + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + if (buf_) + fmt::format_to(appender(*buf_), fmt, std::forward(args)...); + else + fmt::print(file_, fmt, std::forward(args)...); + } +}; + +class string_buffer { + private: + std::string str_; + detail::container_buffer buf_; + + public: + inline string_buffer() : buf_(str_) {} + + inline operator writer() { return buf_; } + inline std::string& str() { return str_; } +}; + +template +struct is_contiguous> : std::true_type { +}; + +// Suppress a misleading warning in older versions of clang. +FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") + +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +class loc_value; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(FILE*, string_view); +} // namespace detail + +namespace detail { +template struct fixed_string { + FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + detail::copy(static_cast(s), s + N, + data); + } + Char data[N] = {}; +}; + +// Converts a compile-time string to basic_string_view. +FMT_EXPORT template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; +} +FMT_EXPORT template +constexpr auto compile_string_to_view(basic_string_view s) + -> basic_string_view { + return s; +} + +// Returns true if value is negative, false otherwise. +// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. +template ::value)> +constexpr auto is_negative(T value) -> bool { + return value < 0; +} +template ::value)> +constexpr auto is_negative(T) -> bool { + return false; +} + +// Smallest of uint32_t, uint64_t, uint128_t that is large enough to +// represent all values of an integral type T. +template +using uint32_or_64_or_128_t = + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, + conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 + +// Converts value in the range [0, 100) to a string. +// GCC generates slightly better code when value is pointer-size. +inline auto digits2(size_t value) -> const char* { + // Align data since unaligned access may be slower when crossing a + // hardware-specific boundary. + alignas(2) static const char data[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + return &data[value * 2]; +} + +template constexpr auto getsign(sign s) -> Char { + return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> + (static_cast(s) * 8)); +} + +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { + int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#if FMT_USE_INT128 +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { + return count_digits_fallback(n); +} +#endif + +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +// Counts the number of digits in n. BITS = log2(radix). +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); +} + +#ifdef FMT_BUILTIN_CLZ +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) + static constexpr uint64_t table[] = { + FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 + FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); +} +#endif + +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +template constexpr auto digits10() noexcept -> int { + return std::numeric_limits::digits10; +} +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } + +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; + +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; +} +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + return thousands_sep_impl(loc); +} + +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { + return Char(decimal_point_impl(loc)); +} +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { + return decimal_point_impl(loc); +} + +#ifndef FMT_HEADER_ONLY +FMT_BEGIN_EXPORT +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto decimal_point_impl(locale_ref) -> char; +extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; +FMT_END_EXPORT +#endif // FMT_HEADER_ONLY + +// Compares two characters for equality. +template auto equal2(const Char* lhs, const char* rhs) -> bool { + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); +} +inline auto equal2(const char* lhs, const char* rhs) -> bool { + return memcmp(lhs, rhs, 2) == 0; +} + +// Writes a two-digit value to out. +template +FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { + if (!is_constant_evaluated() && std::is_same::value && + !FMT_OPTIMIZE_SIZE) { + memcpy(out, digits2(value), 2); + return; + } + *out++ = static_cast('0' + value / 10); + *out = static_cast('0' + value % 10); +} + +// Formats a decimal unsigned integer value writing to out pointing to a buffer +// of specified size. The caller must ensure that the buffer is large enough. +template +FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) + -> Char* { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + unsigned n = to_unsigned(size); + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + n -= 2; + write2digits(out + n, static_cast(value % 100)); + value /= 100; + } + if (value >= 10) { + n -= 2; + write2digits(out + n, static_cast(value)); + } else { + out[--n] = static_cast('0' + value); + } + return out + n; +} + +template +FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, + int num_digits) -> Char* { + do_format_decimal(out, value, num_digits); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + do_format_decimal(ptr, value, num_digits); + return out; + } + // Buffer is large enough to hold all digits (digits10 + 1). + char buffer[digits10() + 1]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + do_format_decimal(buffer, value, num_digits); + return copy_noinline(buffer, buffer + num_digits, out); +} + +template +FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, + int size, bool upper = false) -> Char* { + out += size; + do { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1 << base_bits) - 1)); + *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) + : digits[digit]); + } while ((value >>= base_bits) != 0); + return out; +} + +// Formats an unsigned integer in the power of two base (binary, octal, hex). +template +FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, + int num_digits, bool upper = false) -> Char* { + do_format_base2e(base_bits, out, value, num_digits, upper); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, + int num_digits, bool upper = false) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_base2e(base_bits, ptr, value, num_digits, upper); + return out; + } + // Make buffer large enough for any base. + char buffer[num_bits()]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + format_base2e(base_bits, buffer, value, num_digits, upper); + return detail::copy_noinline(buffer, buffer + num_digits, out); +} + +// A converter from UTF-8 to UTF-16. +class utf8_to_utf16 { + private: + basic_memory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + inline operator basic_string_view() const { + return {&buffer_[0], size()}; + } + inline auto size() const -> size_t { return buffer_.size() - 1; } + inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } + inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } +}; + +enum class to_utf8_error_policy { abort, replace }; + +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template class to_utf8 { + private: + Buffer buffer_; + + public: + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + if (!convert(s, policy)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + operator string_view() const { return string_view(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; + buffer_.push_back(0); + return true; + } + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + continue; + } else { + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } +}; + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline auto floor_log10_pow2(int e) noexcept -> int { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + +inline auto floor_log2_pow10(int e) noexcept -> int { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; +}; + +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 341; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; +}; + +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; + +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; +}; + +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; +}; + +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; +} // namespace dragonbox + +// Returns true iff Float has the implicit bit which is not stored. +template constexpr auto has_implicit_bit() -> bool { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr auto num_significand_bits() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; +} + +// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. +template +FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { + FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); + if (exp < 0) { + *out++ = static_cast('-'); + exp = -exp; + } else { + *out++ = static_cast('+'); + } + auto uexp = static_cast(exp); + if (is_constant_evaluated()) { + if (uexp < 10) *out++ = '0'; + return format_decimal(out, uexp, count_digits(uexp)); + } + if (uexp >= 100u) { + const char* top = digits2(uexp / 100); + if (uexp >= 1000u) *out++ = static_cast(top[0]); + *out++ = static_cast(top[1]); + uexp %= 100; + } + const char* d = digits2(uexp); + *out++ = static_cast(d[0]); + *out++ = static_cast(d[1]); + return out; +} + +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr const int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } + + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); + } +}; + +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { + return {multiply(x.f, y.f), x.e + y.e + 64}; +} + +template () == num_bits()> +using convert_float_result = + conditional_t::value || doublish, double, T>; + +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); +} + +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, + const basic_specs& specs) -> OutputIt { + auto fill_size = specs.fill_size(); + if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); + if (const Char* data = specs.fill()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } + return it; +} + +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { + static_assert(default_align == align::left || default_align == align::right, + ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = + default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; + size_t left_padding = padding >> shifts[static_cast(specs.align())]; + size_t right_padding = padding - left_padding; + auto it = reserve(out, size + padding * specs.fill_size()); + if (left_padding != 0) it = fill(it, left_padding, specs); + it = f(it); + if (right_padding != 0) it = fill(it, right_padding, specs); + return base_iterator(out, it); +} + +template +constexpr auto write_padded(OutputIt out, const format_specs& specs, + size_t size, F&& f) -> OutputIt { + return write_padded(out, specs, size, size, f); +} + +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const format_specs& specs = {}) -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_base2e(4, it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; + if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; + return !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (const_check(!use_utf8)) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_base2e(4, buf, cp, width); + return copy(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': FMT_FALLTHROUGH; + case '\'': FMT_FALLTHROUGH; + case '\\': *out++ = static_cast('\\'); break; + default: + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) + return write_codepoint<4, Char>(out, 'u', escape.cp); + if (escape.cp < 0x110000) + return write_codepoint<8, Char>(out, 'U', escape.cp); + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + +template +auto write_escaped_string(OutputIt out, basic_string_view str) + -> OutputIt { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; +} + +template +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type() == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); + *it++ = value; + return it; + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, + locale_ref loc = {}) -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} + +template class digit_grouping { + private: + std::string grouping_; + std::basic_string thousands_sep_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } + + // Returns the next digit group separator position. + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + } + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + + auto has_separator() const -> bool { return !thousands_sep_.empty(); } + + auto count_separators(int num_digits) const -> int { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + auto apply(Out out, basic_string_view digits) const -> Out { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +// Writes a decimal integer with digit grouping. +template +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_base2e(4, appender(buffer), value, num_digits, specs.upper()); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt() && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_base2e(3, appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_base2e(1, appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); + }); +} + +#if FMT_USE_LOCALE +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +#endif +template +inline auto write_loc(OutputIt, const loc_value&, const format_specs&, + locale_ref) -> bool { + return false; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[static_cast(s)]; + } + return {abs_value, prefix}; +} + +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign()); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +// Size and padding computation separate from write_int to avoid template bloat. +struct size_padding { + unsigned size; + unsigned padding; + + FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, + const format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { + if (specs.align() == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = (prefix >> 24) + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + } + } +}; + +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const format_specs& specs) -> OutputIt { + static_assert(std::is_same>::value, ""); + + constexpr int buffer_size = num_bits(); + char buffer[buffer_size]; + if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); + const char* begin = nullptr; + const char* end = buffer + buffer_size; + + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + begin = do_format_decimal(buffer, abs_value, buffer_size); + break; + case presentation_type::hex: + begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + break; + case presentation_type::oct: { + begin = do_format_base2e(3, buffer, abs_value, buffer_size); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + auto num_digits = end - begin; + if (specs.alt() && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + break; + } + case presentation_type::bin: + begin = do_format_base2e(1, buffer, abs_value, buffer_size); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + break; + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); + } + + // Write an integer in the format + // + // prefix contains chars in three lower bytes and the size in the fourth byte. + int num_digits = static_cast(end - begin); + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return base_iterator(out, copy(begin, end, it)); + } + auto sp = size_padding(num_digits, prefix, specs); + unsigned padding = sp.padding; + return write_padded( + out, specs, sp.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, padding, static_cast('0')); + return copy(begin, end, it); + }); +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs) + -> OutputIt { + return write_int(out, arg, specs); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign()), + specs); +} + +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign()), specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + + bool is_debug = specs.type() == presentation_type::debug; + if (is_debug) { + auto buf = counting_buffer(); + write_escaped_string(basic_appender(buf), s); + size = buf.count(); + } + + size_t width = 0; + if (specs.width != 0) { + width = + is_debug ? size : compute_width(basic_string_view(data, size)); + } + return write_padded( + out, specs, size, width, [=](reserve_iterator it) { + return is_debug ? write_escaped_string(it, s) + : copy(data, data + size, it); + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); +} +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type() == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + if (auto ptr = to_pointer(out, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } + if (negative) *out++ = static_cast('-'); + return format_decimal(out, abs_value, num_digits); +} + +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto alignment = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': alignment = align::left; break; + case '>': alignment = align::right; break; + case '^': alignment = align::center; break; + } + if (alignment != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + report_error("invalid fill character '{'"); + return begin; + } + specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.set_align(alignment); + return begin; +} + +template +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + format_specs specs, sign s) -> OutputIt { + auto str = + isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); + constexpr size_t str_size = 3; + auto size = str_size + (s != sign::none ? 1 : 0); + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + specs.fill_size() == 1 && specs.fill_unit() == '0'; + if (is_zero_fill) specs.set_fill(' '); + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (s != sign::none) + *it++ = detail::getsign(s); + return copy(str, str + str_size, it); + }); +} + +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); +} + +template +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { + return copy(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size); +} +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} + +template ::value)> +inline auto write_significand(Char* out, UInt significand, int significand_size, + int integral_size, Char decimal_point) -> Char* { + if (!decimal_point) return format_decimal(out, significand, significand_size); + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + write2digits(out, static_cast(significand % 100)); + significand /= 100; + } + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; + } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); + return end; +} + +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_noinline(buffer, end, out); +} + +template +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + out = detail::copy_noinline(significand, significand + integral_size, + out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); +} + +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { + auto significand = f.significand; + int significand_size = get_significand_size(f); + const Char zero = static_cast('0'); + size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0); + using iterator = reserve_iterator; + + Char decimal_point = specs.localized() ? detail::decimal_point(loc) + : static_cast('.'); + + int output_exp = f.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (specs.type() == presentation_type::exp) return true; + if (specs.type() == presentation_type::fixed) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (specs.precision > 0 ? specs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (specs.alt()) { + num_zeros = specs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); + } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); + char exp_char = specs.upper() ? 'E' : 'e'; + auto write = [=](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast(exp_char); + return write_exponent(output_exp, it); + }; + return specs.width > 0 + ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); + } + + int exp = f.exponent + significand_size; + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(f.exponent); + int num_zeros = specs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (specs.alt()) { + ++size; + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) + num_zeros = 0; + if (num_zeros > 0) size += to_unsigned(num_zeros); + } + auto grouping = Grouping(loc, specs.localized()); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, significand, significand_size, + f.exponent, grouping); + if (!specs.alt()) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; + size += 1 + static_cast(max_of(num_zeros, 0)); + auto grouping = Grouping(loc, specs.localized()); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && specs.precision >= 0 && + specs.precision < num_zeros) { + num_zeros = specs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); + size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand(it, significand, significand_size); + }); +} + +template class fallback_digit_grouping { + public: + constexpr fallback_digit_grouping(locale_ref, bool) {} + + constexpr auto has_separator() const -> bool { return false; } + + constexpr auto count_separators(int) const -> int { return 0; } + + template + constexpr auto apply(Out out, basic_string_view) const -> Out { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, f, specs, s, loc); + } else { + return do_write_float(out, f, specs, s, loc); + } +} + +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. +} + +template +struct has_isfinite : std::false_type {}; + +template +struct has_isfinite> + : std::true_type {}; + +template ::value&& + has_isfinite::value)> +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { + constexpr T inf = T(std::numeric_limits::infinity()); + if (is_constant_evaluated()) + return !detail::isnan(value) && value < inf && value > -inf; + return std::isfinite(value); +} +template ::value)> +FMT_CONSTEXPR auto isfinite(T value) -> bool { + T inf = T(std::numeric_limits::infinity()); + // std::isfinite doesn't support __float128. + return !detail::isnan(value) && value < inf && value > -inf; +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits >> (num_bits() - 1)) != 0; + } +#endif + } + return std::signbit(static_cast(value)); +} + +inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + if (exp10 > 0 && precision > max_value() - exp10) + FMT_THROW(format_error("number is too big")); + precision += exp10; +} + +class bigint { + private: + // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. + using bigit = uint32_t; // A big digit. + using double_bigit = uint64_t; + enum { bigit_bits = num_bits() }; + enum { bigits_capacity = 32 }; + basic_memory_buffer bigits_; + int exp_; + + friend struct formatter; + + FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { + return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; + } + + FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = double_bigit(bigits_[index]) - other - borrow; + bigits_[index] = static_cast(result); + borrow = static_cast(result >> (bigit_bits * 2 - 1)); + } + + FMT_CONSTEXPR void remove_leading_zeros() { + int num_bigits = static_cast(bigits_.size()) - 1; + while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + FMT_CONSTEXPR void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + if (borrow != 0) subtract_bigits(i, 0, borrow); + FMT_ASSERT(borrow == 0, ""); + remove_leading_zeros(); + } + + FMT_CONSTEXPR void multiply(uint32_t value) { + bigit carry = 0; + const double_bigit wide_value = value; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast(result); + carry = static_cast(result >> bigit_bits); + } + if (carry != 0) bigits_.push_back(carry); + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void multiply(UInt value) { + using half_uint = + conditional_t::value, uint64_t, uint32_t>; + const int shift = num_bits() - bigit_bits; + const UInt lower = static_cast(value); + const UInt upper = value >> num_bits(); + UInt carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + UInt result = lower * bigits_[i] + static_cast(carry); + carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + + (carry >> bigit_bits); + bigits_[i] = static_cast(result); + } + while (carry != 0) { + bigits_.push_back(static_cast(carry)); + carry >>= bigit_bits; + } + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void assign(UInt n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = static_cast(n); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + public: + FMT_CONSTEXPR bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + FMT_CONSTEXPR void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + copy(data, data + size, bigits_.data()); + exp_ = other.exp_; + } + + template FMT_CONSTEXPR void operator=(Int n) { + FMT_ASSERT(n > 0, ""); + assign(uint64_or_128_t(n)); + } + + FMT_CONSTEXPR auto num_bigits() const -> int { + return static_cast(bigits_.size()) + exp_; + } + + FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t(value)); + return *this; + } + + friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { + int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); + if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; + int i = static_cast(b1.bigits_.size()) - 1; + int j = static_cast(b2.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; + if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) -> int { + int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + double_bigit borrow = 0; + int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); + bigit rhs_bigit = rhs.get_bigit(i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + FMT_CONSTEXPR20 void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return *this = 1; + int bitmask = 1 << (num_bits() - + countl_zero(static_cast(exp)) - 1); + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + *this = 5; + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + FMT_CONSTEXPR20 void square() { + int num_bigits = static_cast(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); + bigits_.resize(to_unsigned(num_result_bigits)); + auto sum = uint128_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += double_bigit(n[i]) * n[j]; + } + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += double_bigit(n[i++]) * n[j--]; + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); + } + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + FMT_CONSTEXPR void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +// format_dragon flags. +enum dragon { + predecessor_closer = 1, + fixup = 2, // Run fixup to correct exp10 which can be off by one. + fixed = 4, +}; + +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, + unsigned flags, int num_digits, + buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; + int shift = is_predecessor_closer ? 2 : 1; + if (value.e >= 0) { + numerator = value.f; + numerator <<= value.e + shift; + lower = 1; + lower <<= value.e; + if (is_predecessor_closer) { + upper_store = 1; + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (is_predecessor_closer) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= value.f; + numerator <<= shift; + denominator = 1; + denominator <<= shift - value.e; + } else { + numerator = value.f; + numerator <<= shift; + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower = 1; + if (is_predecessor_closer) { + upper_store = 1ULL << 1; + upper = &upper_store; + } + } + int even = static_cast((value.f & 1) == 0); + if (!upper) upper = &lower; + bool shortest = num_digits < 0; + if ((flags & dragon::fixup) != 0) { + if (add_compare(numerator, *upper, denominator) + even <= 0) { + --exp10; + numerator *= 10; + if (num_digits < 0) { + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (shortest) { + // Generate the shortest representation. + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits <= 0) { + auto digit = '0'; + if (num_digits == 0) { + denominator *= 10; + digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + } + buf.push_back(digit); + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast('0' + digit); +} + +// Formats a floating-point number using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + const auto num_float_significand_bits = detail::num_significand_bits(); + + basic_fp f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit()) --f.e; + + const auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit() ? 1 : 0); + const auto num_xdigits = (num_fraction_bits + 3) / 4; + + const auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (specs.precision >= 0 && print_xdigits > specs.precision) { + const int shift = ((print_xdigits - specs.precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = specs.precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper() ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper() ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast(f.e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + format_hexfloat(static_cast(value), specs, buf); +} + +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; +} + +template +FMT_CONSTEXPR20 auto format_float(Float value, int precision, + const format_specs& specs, bool binary32, + buffer& buf) -> int { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); + auto converted_value = convert_float(value); + + const bool fixed = specs.type() == presentation_type::fixed; + if (value == 0) { + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + fill_n(buf.data(), precision, '0'); + return -precision; + } + + int exp = 0; + bool use_dragon = true; + unsigned dragon_flags = 0; + if (!is_fast_float() || is_constant_evaluated()) { + const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) + using info = dragonbox::float_info; + const auto f = basic_fp(converted_value); + // Compute exp, an approximate power of 10, such that + // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). + // This is based on log10(value) == log2(value) / log2(10) and approximation + // of log2(value) by e + num_fraction_bits idea from double-conversion. + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast(e); + if (e > exp) ++exp; // Compute ceil. + dragon_flags = dragon::fixup; + } else { + // Extract significand bits and exponent bits. + using info = dragonbox::float_info; + auto br = bit_cast(static_cast(value)); + + const uint64_t significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + significand |= + (static_cast(1) << num_significand_bits()); + significand <<= 1; + } else { + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear here"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits() - num_significand_bits(), + ""); + shift -= (num_bits() - num_significand_bits() - 2); + exponent = (std::numeric_limits::min_exponent - + num_significand_bits()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = min_of(precision, 9); + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(720575941)) >> 24) + 1; + digits = static_cast(prod >> 32); + *buffer = static_cast('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(450359963)) >> 20) + 1; + digits = static_cast(prod >> 32); + write2digits(buffer, digits); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast(prod) * static_cast(100); + digits = static_cast(prod >> 32); + write2digits(buffer + number_of_digits_printed, digits); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; + } + } + if (use_dragon) { + auto f = basic_fp(); + bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) + : f.assign(converted_value); + if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; + if (fixed) dragon_flags |= dragon::fixed; + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, dragon_flags, precision, buf, exp); + } + if (!fixed && !specs.alt()) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, + locale_ref loc) -> OutputIt { + // Use signbit because value < 0 is false for NaN. + sign s = detail::signbit(value) ? sign::minus : specs.sign(); + + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isnan(value), specs, s); + + if (specs.align() == align::numeric && s != sign::none) { + *out++ = detail::getsign(s); + s = sign::none; + if (specs.width != 0) --specs.width; + } + + int precision = specs.precision; + if (precision < 0) { + if (specs.type() != presentation_type::none) { + precision = 6; + } else if (is_fast_float::value && !is_constant_evaluated()) { + // Use Dragonbox for the shortest format. + using floaty = conditional_t= sizeof(double), double, float>; + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, s, loc); + } + } + + memory_buffer buffer; + if (specs.type() == presentation_type::hexfloat) { + if (s != sign::none) buffer.push_back(detail::getsign(s)); + format_hexfloat(convert_float(value), specs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); + } + + if (specs.type() == presentation_type::exp) { + if (precision == max_value()) + report_error("number is too big"); + else + ++precision; + if (specs.precision != 0) specs.set_alt(); + } else if (specs.type() == presentation_type::fixed) { + if (specs.precision != 0) specs.set_alt(); + } else if (precision == 0) { + precision = 1; + } + int exp = format_float(convert_float(value), precision, specs, + std::is_same(), buffer); + + specs.precision = precision; + auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, f, specs, s, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + return specs.localized() && write_loc(out, value, specs, loc) + ? out + : write_float(out, value, specs, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { + if (is_constant_evaluated()) return write(out, value, format_specs()); + + auto s = detail::signbit(value) ? sign::minus : sign::none; + + constexpr auto specs = format_specs(); + using floaty = conditional_t= sizeof(double), double, float>; + using floaty_uint = typename dragonbox::float_info::carrier_uint; + floaty_uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), specs, s); + + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, s, {}); +} + +template ::value && + !is_fast_float::value)> +inline auto write(OutputIt out, T value) -> OutputIt { + return write(out, value, format_specs()); +} + +template +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) + -> OutputIt { + FMT_ASSERT(false, ""); + return out; +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) + -> OutputIt { + return copy_noinline(value.begin(), value.end(), out); +} + +template ::value)> +constexpr auto write(OutputIt out, const T& value) -> OutputIt { + return write(out, to_string_view(value)); +} + +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. +template < + typename Char, typename OutputIt, typename T, + bool check = std::is_enum::value && !std::is_same::value && + mapped_type_constant::value != type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + return write(out, static_cast>(value)); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type() != presentation_type::none && + specs.type() != presentation_type::string + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template +FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { + if (value) return write(out, basic_string_view(value)); + report_error("string pointer is null"); + return out; +} + +template ::value)> +auto write(OutputIt out, const T* value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return write_ptr(out, bit_cast(value), &specs); +} + +template ::value == + type::custom_type && + !std::is_fundamental::value)> +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { + auto f = formatter(); + auto parse_ctx = parse_context({}); + f.parse(parse_ctx); + auto ctx = basic_format_context(out, {}, {}); + return f.format(value, ctx); +} + +template +using is_builtin = + bool_constant::value || FMT_BUILTIN_TYPES>; + +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template struct default_arg_formatter { + using context = buffered_context; + + basic_appender out; + + void operator()(monostate) { report_error("argument not found"); } + + template ::value)> + void operator()(T value) { + write(out, value); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg::handle h) { + // Use a null locale since the default format must be unlocalized. + auto parse_ctx = parse_context({}); + auto format_ctx = context(out, {}, {}); + h.format(parse_ctx, format_ctx); + } +}; + +template struct arg_formatter { + basic_appender out; + const format_specs& specs; + FMT_NO_UNIQUE_ADDRESS locale_ref locale; + + template ::value)> + FMT_CONSTEXPR FMT_INLINE void operator()(T value) { + detail::write(out, value, specs, locale); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg>::handle) { + // User-defined types are handled separately because they require access + // to the parse context. + } +}; + +struct dynamic_spec_getter { + template ::value)> + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { + return is_negative(value) ? ~0ull : static_cast(value); + } + + template ::value)> + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + report_error("width/precision is not integer"); + return 0; + } +}; + +template +FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg { + auto arg = ctx.arg(id); + if (!arg) report_error("argument not found"); + return arg; +} + +template +FMT_CONSTEXPR int get_dynamic_spec( + arg_id_kind kind, const arg_ref& ref, + Context& ctx) { + FMT_ASSERT(kind != arg_id_kind::none, ""); + auto arg = + kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); + if (!arg) report_error("argument not found"); + unsigned long long value = arg.visit(dynamic_spec_getter()); + if (value > to_unsigned(max_value())) + report_error("width/precision is out of range"); + return static_cast(value); +} + +template +FMT_CONSTEXPR void handle_dynamic_spec( + arg_id_kind kind, int& value, + const arg_ref& ref, Context& ctx) { + if (kind != arg_id_kind::none) value = get_dynamic_spec(kind, ref, ctx); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct static_named_arg : view { + static constexpr auto name = Str.data; + + const T& value; + static_named_arg(const T& v) : value(v) {} +}; + +template Str> +struct is_named_arg> : std::true_type {}; + +template Str> +struct is_static_named_arg> : std::true_type { +}; + +template Str> +struct udl_arg { + template auto operator=(T&& value) const { + return static_named_arg(std::forward(value)); + } +}; +#else +template struct udl_arg { + const Char* str; + + template auto operator=(T&& value) const -> named_arg { + return {str, std::forward(value)}; + } +}; +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS + +template struct format_handler { + parse_context parse_ctx; + buffered_context ctx; + + void on_text(const Char* begin, const Char* end) { + copy_noinline(begin, end, ctx.out()); + } + + FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + parse_ctx.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + parse_ctx.check_arg_id(id); + int arg_id = ctx.arg_id(id); + if (arg_id < 0) report_error("argument not found"); + return arg_id; + } + + FMT_INLINE void on_replacement_field(int id, const Char*) { + ctx.arg(id).visit(default_arg_formatter{ctx.out()}); + } + + auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + auto arg = get_arg(ctx, id); + // Not using a visitor for custom types gives better codegen. + if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin(); + + auto specs = dynamic_format_specs(); + begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type()); + if (specs.dynamic()) { + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + arg.visit(arg_formatter{ctx.out(), specs, ctx.locale()}); + return begin; + } + + FMT_NORETURN void on_error(const char* message) { report_error(message); } +}; + +using format_func = void (*)(detail::buffer&, int, const char*); +FMT_API void do_report_error(format_func func, int error_code, + const char* message) noexcept; + +FMT_API void format_error_code(buffer& out, int error_code, + string_view message) noexcept; + +template +template +FMT_CONSTEXPR auto native_formatter::format( + const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { + if (!specs_.dynamic()) + return write(ctx.out(), val, specs_, ctx.locale()); + auto specs = format_specs(specs_); + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs_.precision_ref, ctx); + return write(ctx.out(), val, specs, ctx.locale()); +} + +// DEPRECATED! +template struct vformat_args { + using type = basic_format_args>; +}; +template <> struct vformat_args { + using type = format_args; +}; + +template +void vformat_to(buffer& buf, basic_string_view fmt, + typename vformat_args::type args, locale_ref loc = {}) { + auto out = basic_appender(buf); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} +} // namespace detail + +FMT_BEGIN_EXPORT + +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + using char_type = Char; + using iterator = OutputIt; + using parse_context_type FMT_DEPRECATED = parse_context; + template + using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + constexpr generic_context(OutputIt out, + basic_format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) const + -> basic_format_arg { + return args_.get(name); + } + constexpr auto arg_id(basic_string_view name) const -> int { + return args_.get_id(name); + } + + constexpr auto out() const -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + constexpr auto locale() const -> detail::locale_ref { return loc_; } +}; + +class loc_value { + private: + basic_format_arg value_; + + public: + template ::value)> + loc_value(T value) : value_(value) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", std::string grouping = "\3", + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(grouping), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template \ + struct formatter : formatter { \ + template \ + FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter::format(value, ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, detail::long_type); +FMT_FORMAT_AS(unsigned long, detail::ulong_type); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(detail::std_string_view, basic_string_view); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(void*, const void*); + +template +struct formatter : formatter, Char> {}; + +template +class formatter, Char> + : public formatter, Char> {}; + +template +struct formatter, Char> : formatter {}; +template +struct formatter, Char> + : formatter {}; + +template +struct formatter + : detail::native_formatter {}; + +template +struct formatter>> + : formatter, Char> { + template + FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto&& val = format_as(value); // Make an lvalue reference for format. + return formatter, Char>::format(val, ctx); + } +}; + +/** + * Converts `p` to `const void*` for pointer formatting. + * + * **Example**: + * + * auto s = fmt::format("{}", fmt::ptr(p)); + */ +template auto ptr(T p) -> const void* { + static_assert(std::is_pointer::value, ""); + return detail::bit_cast(p); +} + +/** + * Converts `e` to the underlying type. + * + * **Example**: + * + * enum class color { red, green, blue }; + * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" + */ +template +constexpr auto underlying(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} + +namespace enums { +template ::value)> +constexpr auto format_as(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} +} // namespace enums + +#ifdef __cpp_lib_byte +template <> struct formatter : formatter { + static auto format_as(std::byte b) -> unsigned char { + return static_cast(b); + } + template + auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { + return formatter::format(format_as(b), ctx); + } +}; +#endif + +struct bytes { + string_view data; + + inline explicit bytes(string_view s) : data(s) {} +}; + +template <> struct formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::string_type); + } + + template + auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + return detail::write_bytes(ctx.out(), b.data, specs); + } +}; + +// group_digits_view is not derived from view because it copies the argument. +template struct group_digits_view { + T value; +}; + +/** + * Returns a view that formats an integer value using ',' as a + * locale-independent thousands separator. + * + * **Example**: + * + * fmt::print("{}", fmt::group_digits(12345)); + * // Output: "12,345" + */ +template auto group_digits(T value) -> group_digits_view { + return {value}; +} + +template struct formatter> : formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::int_type); + } + + template + auto format(group_digits_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + auto arg = detail::make_write_int_arg(view.value, specs.sign()); + return detail::write_int( + ctx.out(), static_cast>(arg.abs_value), + arg.prefix, specs, detail::digit_grouping("\3", ",")); + } +}; + +template struct nested_view { + const formatter* fmt; + const T* value; +}; + +template +struct formatter, Char> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + template + auto format(nested_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + return view.fmt->format(*view.value, ctx); + } +}; + +template struct nested_formatter { + private: + basic_specs specs_; + int width_; + formatter formatter_; + + public: + constexpr nested_formatter() : width_(0) {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + auto specs = format_specs(); + it = detail::parse_align(it, end, specs); + specs_ = specs; + Char c = *it; + auto width_ref = detail::arg_ref(); + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs, width_ref, ctx); + width_ = specs.width; + } + ctx.advance_to(it); + return formatter_.parse(ctx); + } + + template + auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { + if (width_ == 0) return write(ctx.out()); + auto buf = basic_memory_buffer(); + write(basic_appender(buf)); + auto specs = format_specs(); + specs.width = width_; + specs.set_fill( + basic_string_view(specs_.fill(), specs_.fill_size())); + specs.set_align(specs_.align()); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + auto nested(const T& value) const -> nested_view { + return nested_view{&formatter_, &value}; + } +}; + +inline namespace literals { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template constexpr auto operator""_a() { + using char_t = remove_cvref_t; + return detail::udl_arg(); +} +#else +/** + * User-defined literal equivalent of `fmt::arg`. + * + * **Example**: + * + * using namespace fmt::literals; + * fmt::print("The answer is {answer}.", "answer"_a=42); + */ +constexpr auto operator""_a(const char* s, size_t) -> detail::udl_arg { + return {s}; +} +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS +} // namespace literals + +/// A fast integer formatter. +class format_int { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum { buffer_size = std::numeric_limits::digits10 + 3 }; + mutable char buffer_[buffer_size]; + char* str_; + + template + FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* { + auto n = static_cast>(value); + return detail::do_format_decimal(buffer_, n, buffer_size - 1); + } + + template + FMT_CONSTEXPR20 auto format_signed(Int value) -> char* { + auto abs_value = static_cast>(value); + bool negative = value < 0; + if (negative) abs_value = 0 - abs_value; + auto begin = format_unsigned(abs_value); + if (negative) *--begin = '-'; + return begin; + } + + public: + FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long long value) + : str_(format_unsigned(value)) {} + + /// Returns the number of characters written to the output buffer. + FMT_CONSTEXPR20 auto size() const -> size_t { + return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); + } + + /// Returns a pointer to the output buffer content. No terminating null + /// character is appended. + FMT_CONSTEXPR20 auto data() const -> const char* { return str_; } + + /// Returns a pointer to the output buffer content with terminating null + /// character appended. + FMT_CONSTEXPR20 auto c_str() const -> const char* { + buffer_[buffer_size - 1] = '\0'; + return str_; + } + + /// Returns the content of the output buffer as an `std::string`. + inline auto str() const -> std::string { return {str_, size()}; } +}; + +#define FMT_STRING_IMPL(s, base) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type = fmt::remove_cvref_t; \ + constexpr explicit operator fmt::basic_string_view() const { \ + return fmt::detail::compile_string_to_view(s); \ + } \ + }; \ + using FMT_STRING_VIEW = \ + fmt::basic_string_view; \ + fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ + return FMT_COMPILE_STRING(); \ + }() + +/** + * Constructs a legacy compile-time format string from a string literal `s`. + * + * **Example**: + * + * // A compile-time error because 'd' is an invalid specifier for strings. + * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) + +FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error; + +/** + * Constructs `std::system_error` with a message formatted with + * `fmt::format(fmt, args...)`. + * `error_code` is a system error code as given by `errno`. + * + * **Example**: + * + * // This throws std::system_error with the description + * // cannot open file 'madeup': No such file or directory + * // or similar (system message may vary). + * const char* filename = "madeup"; + * FILE* file = fopen(filename, "r"); + * if (!file) + * throw fmt::system_error(errno, "cannot open file '{}'", filename); + */ +template +auto system_error(int error_code, format_string fmt, T&&... args) + -> std::system_error { + return vsystem_error(error_code, fmt.str, vargs{{args...}}); +} + +/** + * Formats an error message for an error returned by an operating system or a + * language runtime, for example a file opening error, and writes it to `out`. + * The format is the same as the one used by `std::system_error(ec, message)` + * where `ec` is `std::error_code(error_code, std::generic_category())`. + * It is implementation-defined but normally looks like: + * + * : + * + * where `` is the passed message and `` is the system + * message corresponding to the error code. + * `error_code` is a system error code as given by `errno`. + */ +FMT_API void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, const char* message) noexcept; + +inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, fmt, args, loc); + return {buf.data(), buf.size()}; +} + +template +FMT_INLINE auto format(detail::locale_ref loc, format_string fmt, + T&&... args) -> std::string { + return vformat(loc, fmt.str, vargs{{args...}}); +} + +template ::value)> +auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, loc); + return detail::get_iterator(buf, out); +} + +template ::value)> +FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc, + format_string fmt, T&&... args) -> OutputIt { + return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); +} + +template +FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc, + format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); + return buf.count(); +} + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; + +/** + * Formats `args` according to specifications in `fmt` and returns the result + * as a string. + * + * **Example**: + * + * #include + * std::string message = fmt::format("The answer is {}.", 42); + */ +template +FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) + -> std::string { + return vformat(fmt.str, vargs{{args...}}); +} + +/** + * Converts `value` to `std::string` using the default format for type `T`. + * + * **Example**: + * + * std::string answer = fmt::to_string(42); + */ +template ::value)> +FMT_NODISCARD auto to_string(T value) -> std::string { + // The buffer should be large enough to store the number including the sign + // or "false" for bool. + char buffer[max_of(detail::digits10() + 2, 5)]; + return {buffer, detail::write(buffer, value)}; +} + +template ::value)> +FMT_NODISCARD auto to_string(const T& value) -> std::string { + return to_string(format_as(value)); +} + +template ::value && + !detail::use_format_as::value)> +FMT_NODISCARD auto to_string(const T& value) -> std::string { + auto buffer = memory_buffer(); + detail::write(appender(buffer), value); + return {buffer.data(), buffer.size()}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format-inl.h" +#endif + +// Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. +#ifdef FMT_REMOVE_TRANSITIVE_INCLUDES +# undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +#endif + +#endif // FMT_FORMAT_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/os.h b/libs/openal-soft/fmt-11.1.1/include/fmt/os.h new file mode 100644 index 00000000..b2cc5e4b --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/os.h @@ -0,0 +1,427 @@ +// 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_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/ostream.h b/libs/openal-soft/fmt-11.1.1/include/fmt/ostream.h new file mode 100644 index 00000000..5d893c92 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/ostream.h @@ -0,0 +1,166 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#ifndef FMT_MODULE +# include // std::filebuf +#endif + +#ifdef _WIN32 +# ifdef __GLIBCXX__ +# include +# include +# endif +# include +#endif + +#include "chrono.h" // formatbuf + +#ifdef _MSVC_STL_UPDATE +# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE +#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 +# define FMT_MSVC_STL_UPDATE _MSVC_LANG +#else +# define FMT_MSVC_STL_UPDATE 0 +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +// Generate a unique explicit instantion in every translation unit using a tag +// type in an anonymous namespace. +namespace { +struct file_access_tag {}; +} // namespace +template +class file_access { + friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } +}; + +#if FMT_MSVC_STL_UPDATE +template class file_access; +auto get_file(std::filebuf&) -> FILE*; +#endif + +// Write the content of buf to os. +// It is a separate function rather than a part of vprint to simplify testing. +template +void write_buffer(std::basic_ostream& os, buffer& buf) { + const Char* buf_data = buf.data(); + using unsigned_streamsize = make_unsigned_t; + unsigned_streamsize size = buf.size(); + unsigned_streamsize max_size = to_unsigned(max_value()); + do { + unsigned_streamsize n = size <= max_size ? size : max_size; + os.write(buf_data, static_cast(n)); + buf_data += n; + size -= n; + } while (size != 0); +} + +template struct streamed_view { + const T& value; +}; +} // namespace detail + +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct basic_ostream_formatter : formatter, Char> { + void set_debug_format() = delete; + + template + auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { + auto buffer = basic_memory_buffer(); + auto&& formatbuf = detail::formatbuf>(buffer); + auto&& output = std::basic_ostream(&formatbuf); + output.imbue(std::locale::classic()); // The default is always unlocalized. + output << value; + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + return formatter, Char>::format( + {buffer.data(), buffer.size()}, ctx); + } +}; + +using ostream_formatter = basic_ostream_formatter; + +template +struct formatter, Char> + : basic_ostream_formatter { + template + auto format(detail::streamed_view view, Context& ctx) const + -> decltype(ctx.out()) { + return basic_ostream_formatter::format(view.value, ctx); + } +}; + +/** + * Returns a view that formats `value` via an ostream `operator<<`. + * + * **Example**: + * + * fmt::print("Current thread id: {}\n", + * fmt::streamed(std::this_thread::get_id())); + */ +template +constexpr auto streamed(const T& value) -> detail::streamed_view { + return {value}; +} + +inline void vprint(std::ostream& os, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + FILE* f = nullptr; +#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI + if (auto* buf = dynamic_cast(os.rdbuf())) + f = detail::get_file(*buf); +#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI + auto* rdbuf = os.rdbuf(); + if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) + f = sfbuf->file(); + else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) + f = fbuf->file(); +#endif +#ifdef _WIN32 + if (f) { + int fd = _fileno(f); + if (_isatty(fd)) { + os.flush(); + if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; + } + } +#endif + detail::ignore_unused(f); + detail::write_buffer(os, buffer); +} + +/** + * Prints formatted data to the stream `os`. + * + * **Example**: + * + * fmt::print(cerr, "Don't {}!", "panic"); + */ +FMT_EXPORT template +void print(std::ostream& os, format_string fmt, T&&... args) { + fmt::vargs vargs = {{args...}}; + if (detail::use_utf8) return vprint(os, fmt.str, vargs); + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt.str, vargs); + detail::write_buffer(os, buffer); +} + +FMT_EXPORT template +void println(std::ostream& os, format_string fmt, T&&... args) { + fmt::print(os, "{}\n", fmt::format(fmt, std::forward(args)...)); +} + +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/printf.h b/libs/openal-soft/fmt-11.1.1/include/fmt/printf.h new file mode 100644 index 00000000..e7268401 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/printf.h @@ -0,0 +1,633 @@ +// 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_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/ranges.h b/libs/openal-soft/fmt-11.1.1/include/fmt/ranges.h new file mode 100644 index 00000000..118d24fe --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/ranges.h @@ -0,0 +1,848 @@ +// Formatting library for C++ - range and tuple support +// +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +FMT_EXPORT +enum class range_format { disabled, map, set, sequence, string, debug_string }; + +namespace detail { + +template class is_map { + template static auto check(U*) -> typename U::mapped_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value && !is_map::value; +}; + +// C array overload +template +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template +auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template +struct has_member_fn_begin_end_t : std::false_type {}; + +template +struct has_member_fn_begin_end_t().begin()), + decltype(std::declval().end())>> + : std::true_type {}; + +// Member function overloads. +template +auto range_begin(T&& rng) -> decltype(static_cast(rng).begin()) { + return static_cast(rng).begin(); +} +template +auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { + return static_cast(rng).end(); +} + +// ADL overloads. Only participate in overload resolution if member functions +// are not found. +template +auto range_begin(T&& rng) + -> enable_if_t::value, + decltype(begin(static_cast(rng)))> { + return begin(static_cast(rng)); +} +template +auto range_end(T&& rng) -> enable_if_t::value, + decltype(end(static_cast(rng)))> { + return end(static_cast(rng)); +} + +template +struct has_const_begin_end : std::false_type {}; +template +struct has_mutable_begin_end : std::false_type {}; + +template +struct has_const_begin_end< + T, void_t&>())), + decltype(detail::range_end( + std::declval&>()))>> + : std::true_type {}; + +template +struct has_mutable_begin_end< + T, void_t())), + decltype(detail::range_end(std::declval())), + // the extra int here is because older versions of MSVC don't + // SFINAE properly unless there are distinct types + int>> : std::true_type {}; + +template struct is_range_ : std::false_type {}; +template +struct is_range_ + : std::integral_constant::value || + has_mutable_begin_end::value)> {}; + +// tuple_size and tuple_element check. +template class is_tuple_like_ { + template ::type> + static auto check(U* p) -> decltype(std::tuple_size::value, 0); + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 +template +using integer_sequence = std::integer_sequence; +template using index_sequence = std::index_sequence; +template using make_index_sequence = std::make_index_sequence; +#else +template struct integer_sequence { + using value_type = T; + + static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } +}; + +template using index_sequence = integer_sequence; + +template +struct make_integer_sequence : make_integer_sequence {}; +template +struct make_integer_sequence : integer_sequence {}; + +template +using make_index_sequence = make_integer_sequence; +#endif + +template +using tuple_index_sequence = make_index_sequence::value>; + +template ::value> +class is_tuple_formattable_ { + public: + static constexpr const bool value = false; +}; +template class is_tuple_formattable_ { + template + static auto all_true(index_sequence, + integer_sequence= 0)...>) -> std::true_type; + static auto all_true(...) -> std::false_type; + + template + static auto check(index_sequence) -> decltype(all_true( + index_sequence{}, + integer_sequence::type, + C>::value)...>{})); + + public: + static constexpr const bool value = + decltype(check(tuple_index_sequence{}))::value; +}; + +template +FMT_CONSTEXPR void for_each(index_sequence, Tuple&& t, F&& f) { + using std::get; + // Using a free function get(Tuple) now. + const int unused[] = {0, ((void)f(get(t)), 0)...}; + ignore_unused(unused); +} + +template +FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { + for_each(tuple_index_sequence>(), + std::forward(t), std::forward(f)); +} + +template +void for_each2(index_sequence, Tuple1&& t1, Tuple2&& t2, F&& f) { + using std::get; + const int unused[] = {0, ((void)f(get(t1), get(t2)), 0)...}; + ignore_unused(unused); +} + +template +void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { + for_each2(tuple_index_sequence>(), + std::forward(t1), std::forward(t2), + std::forward(f)); +} + +namespace tuple { +// Workaround a bug in MSVC 2019 (v140). +template +using result_t = std::tuple, Char>...>; + +using std::get; +template +auto get_formatters(index_sequence) + -> result_t(std::declval()))...>; +} // namespace tuple + +#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 +// Older MSVC doesn't get the reference type correctly for arrays. +template struct range_reference_type_impl { + using type = decltype(*detail::range_begin(std::declval())); +}; + +template struct range_reference_type_impl { + using type = T&; +}; + +template +using range_reference_type = typename range_reference_type_impl::type; +#else +template +using range_reference_type = + decltype(*detail::range_begin(std::declval())); +#endif + +// We don't use the Range's value_type for anything, but we do need the Range's +// reference type, with cv-ref stripped. +template +using uncvref_type = remove_cvref_t>; + +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); +} +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + +template +struct range_format_kind_ + : std::integral_constant, T>::value + ? range_format::disabled + : is_map::value ? range_format::map + : is_set::value ? range_format::set + : range_format::sequence> {}; + +template +using range_format_constant = std::integral_constant; + +// These are not generic lambdas for compatibility with C++11. +template struct parse_empty_specs { + template FMT_CONSTEXPR void operator()(Formatter& f) { + f.parse(ctx); + detail::maybe_set_debug_format(f, true); + } + parse_context& ctx; +}; +template struct format_tuple_element { + using char_type = typename FormatContext::char_type; + + template + void operator()(const formatter& f, const T& v) { + if (i > 0) ctx.advance_to(detail::copy(separator, ctx.out())); + ctx.advance_to(f.format(v, ctx)); + ++i; + } + + int i; + FormatContext& ctx; + basic_string_view separator; +}; + +} // namespace detail + +template struct is_tuple_like { + static constexpr const bool value = + detail::is_tuple_like_::value && !detail::is_range_::value; +}; + +template struct is_tuple_formattable { + static constexpr const bool value = + detail::is_tuple_formattable_::value; +}; + +template +struct formatter::value && + fmt::is_tuple_formattable::value>> { + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end && detail::to_ascii(*it) == 'n') { + ++it; + set_brackets({}, {}); + set_separator({}); + } + if (it != end && *it != '}') report_error("invalid format specifier"); + ctx.advance_to(it); + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(const Tuple& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + ctx.advance_to(detail::copy(opening_bracket_, ctx.out())); + detail::for_each2( + formatters_, value, + detail::format_tuple_element{0, ctx, separator_}); + return detail::copy(closing_bracket_, ctx.out()); + } +}; + +template struct is_range { + static constexpr const bool value = + detail::is_range_::value && !detail::has_to_string_view::value; +}; + +namespace detail { + +template +using range_formatter_type = formatter, Char>; + +template +using maybe_const_range = + conditional_t::value, const R, R>; + +template +struct is_formattable_delayed + : is_formattable>, Char> {}; +} // namespace detail + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + +template +struct range_formatter; + +template +struct range_formatter< + T, Char, + enable_if_t>, + is_formattable>::value>> { + private: + detail::range_formatter_type underlying_; + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + bool is_debug = false; + + template ::value)> + auto write_debug_string(Output& out, It it, Sentinel end) const -> Output { + auto buf = basic_memory_buffer(); + for (; it != end; ++it) buf.push_back(*it); + auto specs = format_specs(); + specs.set_type(presentation_type::debug); + return detail::write( + out, basic_string_view(buf.data(), buf.size()), specs); + } + + template ::value)> + auto write_debug_string(Output& out, It, Sentinel) const -> Output { + return out; + } + + public: + FMT_CONSTEXPR range_formatter() {} + + FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type& { + return underlying_; + } + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + detail::maybe_set_debug_format(underlying_, true); + if (it == end) return underlying_.parse(ctx); + + switch (detail::to_ascii(*it)) { + case 'n': + set_brackets({}, {}); + ++it; + break; + case '?': + is_debug = true; + set_brackets({}, {}); + ++it; + if (it == end || *it != 's') report_error("invalid format specifier"); + FMT_FALLTHROUGH; + case 's': + if (!std::is_same::value) + report_error("invalid format specifier"); + if (!is_debug) { + set_brackets(detail::string_literal{}, + detail::string_literal{}); + set_separator({}); + detail::maybe_set_debug_format(underlying_, false); + } + ++it; + return it; + } + + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + detail::maybe_set_debug_format(underlying_, false); + ++it; + } + + ctx.advance_to(it); + return underlying_.parse(ctx); + } + + template + auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + auto it = detail::range_begin(range); + auto end = detail::range_end(range); + if (is_debug) return write_debug_string(out, std::move(it), end); + + out = detail::copy(opening_bracket_, out); + int i = 0; + for (; it != end; ++it) { + if (i > 0) out = detail::copy(separator_, out); + ctx.advance_to(out); + auto&& item = *it; // Need an lvalue + out = underlying_.format(item, ctx); + ++i; + } + out = detail::copy(closing_bracket_, out); + return out; + } +}; + +FMT_EXPORT +template +struct range_format_kind + : conditional_t< + is_range::value, detail::range_format_kind_, + std::integral_constant> {}; + +template +struct formatter< + R, Char, + enable_if_t::value != range_format::disabled && + range_format_kind::value != range_format::map && + range_format_kind::value != range_format::string && + range_format_kind::value != range_format::debug_string>, + detail::is_formattable_delayed>::value>> { + private: + using range_type = detail::maybe_const_range; + range_formatter, Char> range_formatter_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR formatter() { + if (detail::const_check(range_format_kind::value != + range_format::set)) + return; + range_formatter_.set_brackets(detail::string_literal{}, + detail::string_literal{}); + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return range_formatter_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + return range_formatter_.format(range, ctx); + } +}; + +// A map formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::map>> { + private: + using map_type = detail::maybe_const_range; + using element_type = detail::uncvref_type; + + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + bool no_delimiters_ = false; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end) { + if (detail::to_ascii(*it) == 'n') { + no_delimiters_ = true; + ++it; + } + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + ++it; + } + ctx.advance_to(it); + } + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + basic_string_view open = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(open, out); + int i = 0; + basic_string_view sep = detail::string_literal{}; + for (auto&& value : map) { + if (i > 0) out = detail::copy(sep, out); + ctx.advance_to(out); + detail::for_each2(formatters_, value, + detail::format_tuple_element{ + 0, ctx, detail::string_literal{}}); + ++i; + } + basic_string_view close = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(close, out); + return out; + } +}; + +// A (debug_)string formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::string || + range_format_kind::value == + range_format::debug_string>> { + private: + using range_type = detail::maybe_const_range; + using string_type = + conditional_t, + decltype(detail::range_begin(std::declval())), + decltype(detail::range_end(std::declval()))>::value, + detail::std_string_view, std::basic_string>; + + formatter underlying_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return underlying_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + out = underlying_.format( + string_type{detail::range_begin(range), detail::range_end(range)}, ctx); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + return out; + } +}; + +template +struct join_view : detail::view { + It begin; + Sentinel end; + basic_string_view sep; + + join_view(It b, Sentinel e, basic_string_view s) + : begin(std::move(b)), end(e), sep(s) {} +}; + +template +struct formatter, Char> { + private: + using value_type = +#ifdef __cpp_lib_ranges + std::iter_value_t; +#else + typename std::iterator_traits::value_type; +#endif + formatter, Char> value_formatter_; + + using view = conditional_t::value, + const join_view, + join_view>; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return value_formatter_.parse(ctx); + } + + template + auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { + using iter = + conditional_t::value, It, It&>; + iter it = value.begin; + auto out = ctx.out(); + if (it == value.end) return out; + out = value_formatter_.format(*it, ctx); + ++it; + while (it != value.end) { + out = detail::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = value_formatter_.format(*it, ctx); + ++it; + } + return out; + } +}; + +template struct tuple_join_view : detail::view { + const Tuple& tuple; + basic_string_view sep; + + tuple_join_view(const Tuple& t, basic_string_view s) + : tuple(t), sep{s} {} +}; + +// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers +// support in tuple_join. It is disabled by default because of issues with +// the dynamic width and precision. +#ifndef FMT_TUPLE_JOIN_SPECIFIERS +# define FMT_TUPLE_JOIN_SPECIFIERS 0 +#endif + +template +struct formatter, Char, + enable_if_t::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx, std::tuple_size()); + } + + template + auto format(const tuple_join_view& value, + FormatContext& ctx) const -> typename FormatContext::iterator { + return do_format(value, ctx, std::tuple_size()); + } + + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + return ctx.begin(); + } + + template + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + auto end = ctx.begin(); +#if FMT_TUPLE_JOIN_SPECIFIERS + end = std::get::value - N>(formatters_).parse(ctx); + if (N > 1) { + auto end1 = do_parse(ctx, std::integral_constant()); + if (end != end1) + report_error("incompatible format specs for tuple elements"); + } +#endif + return end; + } + + template + auto do_format(const tuple_join_view&, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + return ctx.out(); + } + + template + auto do_format(const tuple_join_view& value, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + using std::get; + auto out = + std::get::value - N>(formatters_) + .format(get::value - N>(value.tuple), ctx); + if (N <= 1) return out; + out = detail::copy(value.sep, out); + ctx.advance_to(out); + return do_format(value, ctx, std::integral_constant()); + } +}; + +namespace detail { +// Check if T has an interface like a container adaptor (e.g. std::stack, +// std::queue, std::priority_queue). +template class is_container_adaptor_like { + template static auto check(U* p) -> typename U::container_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template struct all { + const Container& c; + auto begin() const -> typename Container::const_iterator { return c.begin(); } + auto end() const -> typename Container::const_iterator { return c.end(); } +}; +} // namespace detail + +template +struct formatter< + T, Char, + enable_if_t, + bool_constant::value == + range_format::disabled>>::value>> + : formatter, Char> { + using all = detail::all; + template + auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { + struct getter : T { + static auto get(const T& t) -> all { + return {t.*(&getter::c)}; // Access c through the derived class. + } + }; + return formatter::format(getter::get(t), ctx); + } +}; + +FMT_BEGIN_EXPORT + +/// Returns a view that formats the iterator range `[begin, end)` with elements +/// separated by `sep`. +template +auto join(It begin, Sentinel end, string_view sep) -> join_view { + return {std::move(begin), end, sep}; +} + +/** + * Returns a view that formats `range` with elements separated by `sep`. + * + * **Example**: + * + * auto v = std::vector{1, 2, 3}; + * fmt::print("{}", fmt::join(v, ", ")); + * // Output: 1, 2, 3 + * + * `fmt::join` applies passed format specifiers to the range elements: + * + * fmt::print("{:02}", fmt::join(v, ", ")); + * // Output: 01, 02, 03 + */ +template ::value)> +auto join(Range&& r, string_view sep) + -> join_view { + return {detail::range_begin(r), detail::range_end(r), sep}; +} + +/** + * Returns an object that formats `std::tuple` with elements separated by `sep`. + * + * **Example**: + * + * auto t = std::tuple{1, 'a'}; + * fmt::print("{}", fmt::join(t, ", ")); + * // Output: 1, a + */ +template ::value)> +FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +/** + * Returns an object that formats `std::initializer_list` with elements + * separated by `sep`. + * + * **Example**: + * + * fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + * // Output: "1, 2, 3" + */ +template +auto join(std::initializer_list list, string_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/std.h b/libs/openal-soft/fmt-11.1.1/include/fmt/std.h new file mode 100644 index 00000000..b00e4022 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/std.h @@ -0,0 +1,727 @@ +// Formatting library for C++ - formatters for standard library types +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_STD_H_ +#define FMT_STD_H_ + +#include "format.h" +#include "ostream.h" + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. +# if FMT_CPLUSPLUS >= 201703L +# if FMT_HAS_INCLUDE() && \ + (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0) +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# endif +// Use > instead of >= in the version check because may be +// available after C++17 but before C++20 is marked as implemented. +# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE() +# include +# endif +# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE() +# include +# endif +#endif // FMT_MODULE + +#if FMT_HAS_INCLUDE() +# include +#endif + +// GCC 4 does not support FMT_HAS_INCLUDE. +#if FMT_HAS_INCLUDE() || defined(__GLIBCXX__) +# include +// Android NDK with gabi++ library on some architectures does not implement +// abi::__cxa_demangle(). +# ifndef __GABIXX_CXXABI_H__ +# define FMT_HAS_ABI_CXA_DEMANGLE +# endif +#endif + +// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. +#ifndef FMT_CPP_LIB_FILESYSTEM +# ifdef __cpp_lib_filesystem +# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem +# else +# define FMT_CPP_LIB_FILESYSTEM 0 +# endif +#endif + +#ifndef FMT_CPP_LIB_VARIANT +# ifdef __cpp_lib_variant +# define FMT_CPP_LIB_VARIANT __cpp_lib_variant +# else +# define FMT_CPP_LIB_VARIANT 0 +# endif +#endif + +#if FMT_CPP_LIB_FILESYSTEM +FMT_BEGIN_NAMESPACE + +namespace detail { + +template +auto get_path_string(const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && std::is_same_v) + return to_utf8(native, to_utf8_error_policy::replace); + else + return p.string(); +} + +template +void write_escaped_path(basic_memory_buffer& quoted, + const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && + std::is_same_v) { + auto buf = basic_memory_buffer(); + write_escaped_string(std::back_inserter(buf), native); + bool valid = to_utf8::convert(quoted, {buf.data(), buf.size()}); + FMT_ASSERT(valid, "invalid utf16"); + } else if constexpr (std::is_same_v) { + write_escaped_string( + std::back_inserter(quoted), native); + } else { + write_escaped_string(std::back_inserter(quoted), p.string()); + } +} + +} // namespace detail + +FMT_EXPORT +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + bool debug_ = false; + char path_type_ = 0; + + public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + + FMT_CONSTEXPR auto parse(parse_context& ctx) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++); + return it; + } + + template + auto format(const std::filesystem::path& p, FormatContext& ctx) const { + auto specs = specs_; + auto path_string = + !path_type_ ? p.native() + : p.generic_string(); + + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + if (!debug_) { + auto s = detail::get_path_string(p, path_string); + return detail::write(ctx.out(), basic_string_view(s), specs); + } + auto quoted = basic_memory_buffer(); + detail::write_escaped_path(quoted, p, path_string); + return detail::write(ctx.out(), + basic_string_view(quoted.data(), quoted.size()), + specs); + } +}; + +class path : public std::filesystem::path { + public: + auto display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{}"), base); + } + auto system_string() const -> std::string { return string(); } + + auto generic_display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{:g}"), base); + } + auto generic_system_string() const -> std::string { return generic_string(); } +}; + +FMT_END_NAMESPACE +#endif // FMT_CPP_LIB_FILESYSTEM + +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template +struct formatter, Char> : nested_formatter { + private: + // Functor because C++11 doesn't support generic lambdas. + struct writer { + const std::bitset& bs; + + template + FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { + for (auto pos = N; pos > 0; --pos) { + out = detail::write(out, bs[pos - 1] ? Char('1') : Char('0')); + } + + return out; + } + }; + + public: + template + auto format(const std::bitset& bs, FormatContext& ctx) const + -> decltype(ctx.out()) { + return write_padded(ctx, writer{bs}); + } +}; + +FMT_EXPORT +template +struct formatter : basic_ostream_formatter {}; +FMT_END_NAMESPACE + +#ifdef __cpp_lib_optional +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template +struct formatter, Char, + std::enable_if_t::value>> { + private: + formatter underlying_; + static constexpr basic_string_view optional = + detail::string_literal{}; + static constexpr basic_string_view none = + detail::string_literal{}; + + template + FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) + -> decltype(u.set_debug_format(set)) { + u.set_debug_format(set); + } + + template + FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) { + maybe_set_debug_format(underlying_, true); + return underlying_.parse(ctx); + } + + template + auto format(const std::optional& opt, FormatContext& ctx) const + -> decltype(ctx.out()) { + if (!opt) return detail::write(ctx.out(), none); + + auto out = ctx.out(); + out = detail::write(out, optional); + ctx.advance_to(out); + out = underlying_.format(*opt, ctx); + return detail::write(out, ')'); + } +}; +FMT_END_NAMESPACE +#endif // __cpp_lib_optional + +#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { + if constexpr (has_to_string_view::value) + return write_escaped_string(out, detail::to_string_view(v)); + if constexpr (std::is_same_v) return write_escaped_char(out, v); + return write(out, v); +} + +} // namespace detail + +FMT_END_NAMESPACE +#endif + +#ifdef __cpp_lib_expected +FMT_BEGIN_NAMESPACE + +FMT_EXPORT +template +struct formatter, Char, + std::enable_if_t<(std::is_void::value || + is_formattable::value) && + is_formattable::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::expected& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + if (value.has_value()) { + out = detail::write(out, "expected("); + if constexpr (!std::is_void::value) + out = detail::write_escaped_alternative(out, *value); + } else { + out = detail::write(out, "unexpected("); + out = detail::write_escaped_alternative(out, value.error()); + } + *out++ = ')'; + return out; + } +}; +FMT_END_NAMESPACE +#endif // __cpp_lib_expected + +#ifdef __cpp_lib_source_location +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <> struct formatter { + FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } + + template + auto format(const std::source_location& loc, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write(out, loc.file_name()); + out = detail::write(out, ':'); + out = detail::write(out, loc.line()); + out = detail::write(out, ':'); + out = detail::write(out, loc.column()); + out = detail::write(out, ": "); + out = detail::write(out, loc.function_name()); + return out; + } +}; +FMT_END_NAMESPACE +#endif + +#if FMT_CPP_LIB_VARIANT +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using variant_index_sequence = + std::make_index_sequence::value>; + +template struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_> : std::true_type {}; + +// formattable element check. +template class is_variant_formattable_ { + template + static std::conjunction< + is_formattable, C>...> + check(std::index_sequence); + + public: + static constexpr const bool value = + decltype(check(variant_index_sequence{}))::value; +}; + +} // namespace detail + +template struct is_variant_like { + static constexpr const bool value = detail::is_variant_like_::value; +}; + +template struct is_variant_formattable { + static constexpr const bool value = + detail::is_variant_formattable_::value; +}; + +FMT_EXPORT +template struct formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::monostate&, FormatContext& ctx) const + -> decltype(ctx.out()) { + return detail::write(ctx.out(), "monostate"); + } +}; + +FMT_EXPORT +template +struct formatter< + Variant, Char, + std::enable_if_t, is_variant_formattable>>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const Variant& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + out = detail::write(out, "variant("); + FMT_TRY { + std::visit( + [&](const auto& v) { + out = detail::write_escaped_alternative(out, v); + }, + value); + } + FMT_CATCH(const std::bad_variant_access&) { + detail::write(out, "valueless by exception"); + } + *out++ = ')'; + return out; + } +}; +FMT_END_NAMESPACE +#endif // FMT_CPP_LIB_VARIANT + +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <> struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + char c = *it; + if ((c >= '0' && c <= '9') || c == '{') + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + return it; + } + + template + FMT_CONSTEXPR20 auto format(const std::error_code& ec, + FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + memory_buffer buf; + buf.append(string_view(ec.category().name())); + buf.push_back(':'); + detail::write(appender(buf), ec.value()); + return detail::write(ctx.out(), string_view(buf.data(), buf.size()), + specs); + } +}; + +#if FMT_USE_RTTI +namespace detail { + +template +auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { +# ifdef FMT_HAS_ABI_CXA_DEMANGLE + int status = 0; + std::size_t size = 0; + std::unique_ptr demangled_name_ptr( + abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); + + string_view demangled_name_view; + if (demangled_name_ptr) { + demangled_name_view = demangled_name_ptr.get(); + + // Normalization of stdlib inline namespace names. + // libc++ inline namespaces. + // std::__1::* -> std::* + // std::__1::__fs::* -> std::* + // libstdc++ inline namespaces. + // std::__cxx11::* -> std::* + // std::filesystem::__cxx11::* -> std::filesystem::* + if (demangled_name_view.starts_with("std::")) { + char* begin = demangled_name_ptr.get(); + char* to = begin + 5; // std:: + for (char *from = to, *end = begin + demangled_name_view.size(); + from < end;) { + // This is safe, because demangled_name is NUL-terminated. + if (from[0] == '_' && from[1] == '_') { + char* next = from + 1; + while (next < end && *next != ':') next++; + if (next[0] == ':' && next[1] == ':') { + from = next + 2; + continue; + } + } + *to++ = *from++; + } + demangled_name_view = {begin, detail::to_unsigned(to - begin)}; + } + } else { + demangled_name_view = string_view(ti.name()); + } + return detail::write_bytes(out, demangled_name_view); +# elif FMT_MSC_VERSION + const string_view demangled_name(ti.name()); + for (std::size_t i = 0; i < demangled_name.size(); ++i) { + auto sub = demangled_name; + sub.remove_prefix(i); + if (sub.starts_with("enum ")) { + i += 4; + continue; + } + if (sub.starts_with("class ") || sub.starts_with("union ")) { + i += 5; + continue; + } + if (sub.starts_with("struct ")) { + i += 6; + continue; + } + if (*sub.begin() != ' ') *out++ = *sub.begin(); + } + return out; +# else + return detail::write_bytes(out, string_view(ti.name())); +# endif +} + +} // namespace detail + +FMT_EXPORT +template +struct formatter { + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::type_info& ti, Context& ctx) const + -> decltype(ctx.out()) { + return detail::write_demangled_name(ctx.out(), ti); + } +}; +#endif + +FMT_EXPORT +template +struct formatter< + T, Char, // DEPRECATED! Mixing code unit types. + typename std::enable_if::value>::type> { + private: + bool with_typename_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it == end || *it == '}') return it; + if (*it == 't') { + ++it; + with_typename_ = FMT_USE_RTTI != 0; + } + return it; + } + + template + auto format(const std::exception& ex, Context& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); +#if FMT_USE_RTTI + if (with_typename_) { + out = detail::write_demangled_name(out, typeid(ex)); + *out++ = ':'; + *out++ = ' '; + } +#endif + return detail::write_bytes(out, string_view(ex.what())); + } +}; + +namespace detail { + +template +struct has_flip : std::false_type {}; + +template +struct has_flip().flip())>> + : std::true_type {}; + +template struct is_bit_reference_like { + static constexpr const bool value = + std::is_convertible::value && + std::is_nothrow_assignable::value && has_flip::value; +}; + +#ifdef _LIBCPP_VERSION + +// Workaround for libc++ incompatibility with C++ standard. +// According to the Standard, `bitset::operator[] const` returns bool. +template +struct is_bit_reference_like> { + static constexpr const bool value = true; +}; + +#endif + +} // namespace detail + +// We can't use std::vector::reference and +// std::bitset::reference because the compiler can't deduce Allocator and N +// in partial specialization. +FMT_EXPORT +template +struct formatter::value>> + : formatter { + template + FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v, ctx); + } +}; + +template +auto ptr(const std::unique_ptr& p) -> const void* { + return p.get(); +} +template auto ptr(const std::shared_ptr& p) -> const void* { + return p.get(); +} + +FMT_EXPORT +template +struct formatter, Char, + enable_if_t::value>> + : formatter { + template + auto format(const std::atomic& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.load(), ctx); + } +}; + +#ifdef __cpp_lib_atomic_flag_test +FMT_EXPORT +template +struct formatter : formatter { + template + auto format(const std::atomic_flag& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.test(), ctx); + } +}; +#endif // __cpp_lib_atomic_flag_test + +FMT_EXPORT +template struct formatter, Char> { + private: + detail::dynamic_format_specs specs_; + + template + FMT_CONSTEXPR auto do_format(const std::complex& c, + detail::dynamic_format_specs& specs, + FormatContext& ctx, OutputIt out) const + -> OutputIt { + if (c.real() != 0) { + *out++ = Char('('); + out = detail::write(out, c.real(), specs, ctx.locale()); + specs.set_sign(sign::plus); + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + *out++ = Char(')'); + return out; + } + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + return out; + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type_constant::value); + } + + template + auto format(const std::complex& c, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + if (specs.dynamic()) { + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); + auto buf = basic_memory_buffer(); + + auto outer_specs = format_specs(); + outer_specs.width = specs.width; + auto fill = specs.template fill(); + if (fill) + outer_specs.set_fill(basic_string_view(fill, specs.fill_size())); + outer_specs.set_align(specs.align()); + + specs.width = 0; + specs.set_fill({}); + specs.set_align(align::none); + + do_format(c, specs, ctx, basic_appender(buf)); + return detail::write(ctx.out(), + basic_string_view(buf.data(), buf.size()), + outer_specs); + } +}; + +FMT_EXPORT +template +struct formatter, Char, + enable_if_t, Char>::value>> + : formatter, Char> { + template + auto format(std::reference_wrapper ref, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format(ref.get(), ctx); + } +}; + +FMT_END_NAMESPACE +#endif // FMT_STD_H_ diff --git a/libs/openal-soft/fmt-11.1.1/include/fmt/xchar.h b/libs/openal-soft/fmt-11.1.1/include/fmt/xchar.h new file mode 100644 index 00000000..4cbda542 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/include/fmt/xchar.h @@ -0,0 +1,368 @@ +// Formatting library for C++ - optional wchar_t and exotic character support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ + +#include "color.h" +#include "format.h" +#include "ostream.h" +#include "ranges.h" + +#ifndef FMT_MODULE +# include +# if FMT_USE_LOCALE +# include +# endif +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using is_exotic_char = bool_constant::value>; + +template struct format_string_char {}; + +template +struct format_string_char< + S, void_t())))>> { + using type = char_t; +}; + +template +struct format_string_char< + S, enable_if_t::value>> { + using type = typename S::char_type; +}; + +template +using format_string_char_t = typename format_string_char::type; + +inline auto write_loc(basic_appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#if FMT_USE_LOCALE + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return value.visit(loc_writer{out, specs, separator, grouping, {}}); +#endif + return false; +} +} // namespace detail + +FMT_BEGIN_EXPORT + +using wstring_view = basic_string_view; +using wformat_parse_context = parse_context; +using wformat_context = buffered_context; +using wformat_args = basic_format_args; +using wmemory_buffer = basic_memory_buffer; + +template struct basic_fstring { + private: + basic_string_view str_; + + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + Char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + using t = basic_fstring; + + template >::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) { + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(s, checker(s, arg_pack())); + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) { + FMT_CONSTEXPR auto sv = basic_string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + basic_fstring(runtime_format_string fmt) : str_(fmt.str) {} + + operator basic_string_view() const { return str_; } + auto get() const -> basic_string_view { return str_; } +}; + +template +using basic_format_string = basic_fstring; + +template +using wformat_string = typename basic_format_string::t; +inline auto runtime(wstring_view s) -> runtime_format_string { + return {{s}}; +} + +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; + +#ifdef __cpp_char8_t +template <> struct is_char : bool_constant {}; +#endif + +template +constexpr auto make_wformat_args(T&... args) + -> decltype(fmt::make_format_args(args...)) { + return fmt::make_format_args(args...); +} + +#if !FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg { + return {s}; +} +} // namespace literals +#endif + +template +auto join(It begin, Sentinel end, wstring_view sep) + -> join_view { + return {begin, end, sep}; +} + +template ::value)> +auto join(Range&& range, wstring_view sep) + -> join_view { + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(std::initializer_list list, wstring_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +template ::value)> +auto join(const Tuple& tuple, basic_string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +template ::value)> +auto vformat(basic_string_view fmt, + typename detail::vformat_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, fmt, args); + return {buf.data(), buf.size()}; +} + +template +auto format(wformat_string fmt, T&&... args) -> std::wstring { + return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +auto format_to(OutputIt out, wformat_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt::wstring_view(fmt), + fmt::make_wformat_args(args...)); +} + +// Pass char_t as a default template parameter instead of using +// std::basic_string> to reduce the symbol size. +template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_same::value)> +auto format(const S& fmt, T&&... args) -> std::basic_string { + return vformat(detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto vformat(detail::locale_ref loc, const S& fmt, + typename detail::vformat_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), args, + detail::locale_ref(loc)); + return {buf.data(), buf.size()}; +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto format(detail::locale_ref loc, const S& fmt, T&&... args) + -> std::basic_string { + return vformat(loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +auto vformat_to(OutputIt out, const S& fmt, + typename detail::vformat_args::type args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, detail::to_string_view(fmt), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value && + !std::is_same::value && + !std::is_same::value)> +inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { + return vformat_to(out, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt, + typename detail::vformat_args::type args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template , + bool enable = detail::is_output_iterator::value && + detail::is_exotic_char::value> +inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt, + T&&... args) -> + typename std::enable_if::type { + return vformat_to(out, loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template ::value&& + detail::is_exotic_char::value)> +inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view fmt, + typename detail::vformat_args::type args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args); + return {buf.out(), buf.count()}; +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result { + return vformat_to_n(out, n, fmt::basic_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); + return buf.count(); +} + +inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, fmt, args); + buf.push_back(L'\0'); + if (std::fputws(buf.data(), f) == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +inline void vprint(wstring_view fmt, wformat_args args) { + vprint(stdout, fmt, args); +} + +template +void print(std::FILE* f, wformat_string fmt, T&&... args) { + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template void print(wformat_string fmt, T&&... args) { + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +void println(std::FILE* f, wformat_string fmt, T&&... args) { + return print(f, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +template void println(wformat_string fmt, T&&... args) { + return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) + -> std::wstring { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return {buf.data(), buf.size()}; +} + +template +inline auto format(const text_style& ts, wformat_string fmt, T&&... args) + -> std::wstring { + return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, + wformat_string fmt, const T&... args) { + vprint(f, ts, fmt, fmt::make_wformat_args(args...)); +} + +template +FMT_DEPRECATED void print(const text_style& ts, wformat_string fmt, + const T&... args) { + return print(stdout, ts, fmt, args...); +} + +inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { + auto buffer = basic_memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::write_buffer(os, buffer); +} + +template +void print(std::wostream& os, wformat_string fmt, T&&... args) { + vprint(os, fmt, fmt::make_format_args>(args...)); +} + +template +void println(std::wostream& os, wformat_string fmt, T&&... args) { + print(os, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +/// Converts `value` to `std::wstring` using the default format for type `T`. +template inline auto to_wstring(const T& value) -> std::wstring { + return format(FMT_STRING(L"{}"), value); +} +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_XCHAR_H_ diff --git a/libs/openal-soft/fmt-11.1.1/src/fmt.cc b/libs/openal-soft/fmt-11.1.1/src/fmt.cc new file mode 100644 index 00000000..2dc4ef2f --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/src/fmt.cc @@ -0,0 +1,151 @@ +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 diff --git a/libs/openal-soft/fmt-11.1.1/src/format.cc b/libs/openal-soft/fmt-11.1.1/src/format.cc new file mode 100644 index 00000000..3ccd8068 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/src/format.cc @@ -0,0 +1,46 @@ +// 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 diff --git a/libs/openal-soft/fmt-11.1.1/src/os.cc b/libs/openal-soft/fmt-11.1.1/src/os.cc new file mode 100644 index 00000000..c833a051 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/src/os.cc @@ -0,0 +1,398 @@ +// 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 diff --git a/libs/openal-soft/fmt-11.1.1/support/Android.mk b/libs/openal-soft/fmt-11.1.1/support/Android.mk new file mode 100644 index 00000000..84a3e32f --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/Android.mk @@ -0,0 +1,15 @@ +LOCAL_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) + diff --git a/libs/openal-soft/fmt-11.1.1/support/AndroidManifest.xml b/libs/openal-soft/fmt-11.1.1/support/AndroidManifest.xml new file mode 100644 index 00000000..c282ef5a --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/libs/openal-soft/fmt-11.1.1/support/C++.sublime-syntax b/libs/openal-soft/fmt-11.1.1/support/C++.sublime-syntax new file mode 100644 index 00000000..9dfb5cbe --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/C++.sublime-syntax @@ -0,0 +1,2061 @@ +%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 diff --git a/libs/openal-soft/fmt-11.1.1/support/README b/libs/openal-soft/fmt-11.1.1/support/README new file mode 100644 index 00000000..468f5485 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/README @@ -0,0 +1,4 @@ +This directory contains build support files such as + +* CMake modules +* Build scripts diff --git a/libs/openal-soft/fmt-11.1.1/support/Vagrantfile b/libs/openal-soft/fmt-11.1.1/support/Vagrantfile new file mode 100644 index 00000000..9d0ff6a5 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/Vagrantfile @@ -0,0 +1,19 @@ +# -*- 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 diff --git a/libs/openal-soft/fmt-11.1.1/support/bazel/.bazelversion b/libs/openal-soft/fmt-11.1.1/support/bazel/.bazelversion new file mode 100644 index 00000000..a8a18875 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/bazel/.bazelversion @@ -0,0 +1 @@ +7.1.2 diff --git a/libs/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel b/libs/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel new file mode 100644 index 00000000..1a06ed52 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/bazel/BUILD.bazel @@ -0,0 +1,20 @@ +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"], +) diff --git a/libs/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel b/libs/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel new file mode 100644 index 00000000..6023f362 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/bazel/MODULE.bazel @@ -0,0 +1,6 @@ +module( + name = "fmt", + compatibility_level = 10, +) + +bazel_dep(name = "platforms", version = "0.0.10") diff --git a/libs/openal-soft/fmt-11.1.1/support/bazel/README.md b/libs/openal-soft/fmt-11.1.1/support/bazel/README.md new file mode 100644 index 00000000..44508f1c --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/bazel/README.md @@ -0,0 +1,28 @@ +# Bazel support + +To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, +`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. +This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). + +## Using {fmt} as a dependency + +### Using Bzlmod + +The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}. + +For instance, to use {fmt} add to your `MODULE.bazel` file: + +``` +bazel_dep(name = "fmt", version = "10.2.1") +``` + +### Live at head + +For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.: + +``` +local_path_override( + module_name = "fmt", + path = "../third_party/fmt", +) +``` diff --git a/libs/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel b/libs/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel new file mode 100644 index 00000000..b1be18f3 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/bazel/WORKSPACE.bazel @@ -0,0 +1,2 @@ +# WORKSPACE marker file needed by Bazel + diff --git a/libs/openal-soft/fmt-11.1.1/support/build.gradle b/libs/openal-soft/fmt-11.1.1/support/build.gradle new file mode 100644 index 00000000..c5126d05 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/build.gradle @@ -0,0 +1,132 @@ +import 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" + } +} diff --git a/libs/openal-soft/fmt-11.1.1/support/check-commits b/libs/openal-soft/fmt-11.1.1/support/check-commits new file mode 100644 index 00000000..11472d41 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/check-commits @@ -0,0 +1,43 @@ +#!/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() diff --git a/libs/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake b/libs/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake new file mode 100644 index 00000000..4e2da540 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/cmake/FindSetEnv.cmake @@ -0,0 +1,7 @@ +# 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 () diff --git a/libs/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake b/libs/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake new file mode 100644 index 00000000..32d6d668 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/cmake/JoinPaths.cmake @@ -0,0 +1,26 @@ +# 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() diff --git a/libs/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in b/libs/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in new file mode 100644 index 00000000..bc1684f2 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/cmake/fmt-config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +if (NOT TARGET fmt::fmt) + include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) +endif () + +check_required_components(fmt) diff --git a/libs/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in b/libs/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in new file mode 100644 index 00000000..29976a8a --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/cmake/fmt.pc.in @@ -0,0 +1,11 @@ +prefix=@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} + diff --git a/libs/openal-soft/fmt-11.1.1/support/docopt.py b/libs/openal-soft/fmt-11.1.1/support/docopt.py new file mode 100644 index 00000000..2e43f7ce --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/docopt.py @@ -0,0 +1,581 @@ +"""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() diff --git a/libs/openal-soft/fmt-11.1.1/support/mkdocs b/libs/openal-soft/fmt-11.1.1/support/mkdocs new file mode 100644 index 00000000..e554c1fd --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/mkdocs @@ -0,0 +1,76 @@ +#!/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)) diff --git a/libs/openal-soft/fmt-11.1.1/support/mkdocs.yml b/libs/openal-soft/fmt-11.1.1/support/mkdocs.yml new file mode 100644 index 00000000..17034cd1 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/mkdocs.yml @@ -0,0 +1,48 @@ +site_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 diff --git a/libs/openal-soft/fmt-11.1.1/support/printable.py b/libs/openal-soft/fmt-11.1.1/support/printable.py new file mode 100644 index 00000000..8fa86b30 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/printable.py @@ -0,0 +1,201 @@ +#!/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() diff --git a/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py b/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py new file mode 100644 index 00000000..85d52a58 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py @@ -0,0 +1,338 @@ +# 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) diff --git a/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README b/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README new file mode 100644 index 00000000..7da18db5 --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README @@ -0,0 +1 @@ +mkdocsstrings requires a handler to have a templates directory. diff --git a/libs/openal-soft/fmt-11.1.1/support/release.py b/libs/openal-soft/fmt-11.1.1/support/release.py new file mode 100644 index 00000000..26de7f4f --- /dev/null +++ b/libs/openal-soft/fmt-11.1.1/support/release.py @@ -0,0 +1,188 @@ +#!/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]) diff --git a/libs/openal-soft/include/AL/al.h b/libs/openal-soft/include/AL/al.h index e9f8f3b1..a4e3ad51 100644 --- a/libs/openal-soft/include/AL/al.h +++ b/libs/openal-soft/include/AL/al.h @@ -5,9 +5,19 @@ #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 -#if __cplusplus >= 201703L +#else +#define AL_API_NOEXCEPT +#endif +#if AL_CPLUSPLUS >= 201703L #define AL_API_NOEXCEPT17 noexcept #else #define AL_API_NOEXCEPT17 @@ -19,6 +29,8 @@ extern "C" { #define AL_API_NOEXCEPT17 #endif +#undef AL_CPLUSPLUS + #else /* __cplusplus */ #define AL_API_NOEXCEPT diff --git a/libs/openal-soft/include/AL/alc.h b/libs/openal-soft/include/AL/alc.h index 3311b57f..d048ca04 100644 --- a/libs/openal-soft/include/AL/alc.h +++ b/libs/openal-soft/include/AL/alc.h @@ -5,9 +5,19 @@ #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 -#if __cplusplus >= 201703L +#else +#define ALC_API_NOEXCEPT +#endif +#if ALC_CPLUSPLUS >= 201703L #define ALC_API_NOEXCEPT17 noexcept #else #define ALC_API_NOEXCEPT17 @@ -19,6 +29,8 @@ extern "C" { #define ALC_API_NOEXCEPT17 #endif +#undef ALC_CPLUSPLUS + #else /* __cplusplus */ #define ALC_API_NOEXCEPT diff --git a/libs/openal-soft/include/AL/alext.h b/libs/openal-soft/include/AL/alext.h index 37680fb6..3908e194 100644 --- a/libs/openal-soft/include/AL/alext.h +++ b/libs/openal-soft/include/AL/alext.h @@ -704,15 +704,19 @@ 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 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 @@ -787,14 +791,14 @@ typedef void (AL_APIENTRY *LPALGETSOURCEFVDIRECT)(ALCcontext *context, ALuint so 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 *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 *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; @@ -858,6 +862,8 @@ typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPDIRECTEXT)(ALCcontext *context) AL_AP typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGDIRECTEXT)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALGETPOINTERDIRECTEXT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETPOINTERVDIRECTEXT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; /* AL_EXT_FOLDBACK */ typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTARTDIRECT)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTOPDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; @@ -900,7 +906,7 @@ typedef ALenum (AL_APIENTRY *LPEAXGETDIRECT)(ALCcontext *context, const struct _ 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* AL_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALchar *funcName) AL_API_NOEXCEPT; +ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) AL_API_NOEXCEPT; void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; @@ -1019,14 +1025,16 @@ void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint ef 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 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; @@ -1071,6 +1079,11 @@ ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, AL #endif #endif +#ifndef AL_SOFT_bformat_hoa +#define AL_SOFT_bformat_hoa +#define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D +#endif + #ifdef __cplusplus } #endif diff --git a/libs/openal-soft/libopenal.version b/libs/openal-soft/libopenal.version index eee82f63..dd998e14 100644 --- a/libs/openal-soft/libopenal.version +++ b/libs/openal-soft/libopenal.version @@ -188,8 +188,5 @@ alSourcei64vSOFT; alSourceQueueBufferLayersSOFT; alUnmapBufferSOFT; -# love2d-mod -Java_org_love2d_android_GameActivity_nativeSetDefaultStreamValues; - local: *; }; diff --git a/libs/openal-soft/opensl_latency.cpp b/libs/openal-soft/opensl_latency.cpp deleted file mode 100644 index e0274a5d..00000000 --- a/libs/openal-soft/opensl_latency.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// This file is NOT part of OpenAL-soft - -#include - -#include "oboe/Oboe.h" - -extern "C" -{ - -JNIEXPORT void JNICALL -Java_org_love2d_android_GameActivity_nativeSetDefaultStreamValues( - JNIEnv *env, - jclass type, - jint sampleRate, - jint framesPerBurst -) { - oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate; - oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst; -} - -} diff --git a/libs/openal-soft/router/alc.cpp b/libs/openal-soft/router/alc.cpp index 58ba4464..29244d13 100644 --- a/libs/openal-soft/router/alc.cpp +++ b/libs/openal-soft/router/alc.cpp @@ -1,27 +1,38 @@ #include "config.h" -#include -#include -#include -#include - #include #include +#include +#include +#include +#include #include +#include +#include #include +#include #include "AL/alc.h" -#include "alstring.h" + +#include "almalloc.h" #include "router.h" +#include "strutils.h" -#define DECL(x) { #x, reinterpret_cast(x) } +namespace { + +using namespace std::string_view_literals; + +std::once_flag InitOnce; +void LoadDrivers() { std::call_once(InitOnce, []{ LoadDriverList(); }); } + struct FuncExportEntry { const char *funcName; void *address; }; -static const std::array alcFunctions{{ +#define DECL(x) FuncExportEntry{ #x, reinterpret_cast(x) } +const std::array alcFunctions{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), @@ -154,15 +165,15 @@ static const std::array alcFunctions{{ DECL(alGetAuxiliaryEffectSlotfv), DECL(alGetAuxiliaryEffectSloti), DECL(alGetAuxiliaryEffectSlotiv), -}}; +}; #undef DECL -#define DECL(x) { #x, (x) } struct EnumExportEntry { const ALCchar *enumName; ALCenum value; }; -static const std::array alcEnumerations{{ +#define DECL(x) EnumExportEntry{ #x, (x) } +const std::array alcEnumerations{ DECL(ALC_INVALID), DECL(ALC_FALSE), DECL(ALC_TRUE), @@ -268,92 +279,108 @@ static const std::array alcEnumerations{{ DECL(AL_LINEAR_DISTANCE_CLAMPED), DECL(AL_EXPONENT_DISTANCE), DECL(AL_EXPONENT_DISTANCE_CLAMPED), -}}; +}; #undef DECL -static const ALCchar alcNoError[] = "No Error"; -static const ALCchar alcErrInvalidDevice[] = "Invalid Device"; -static const ALCchar alcErrInvalidContext[] = "Invalid Context"; -static const ALCchar alcErrInvalidEnum[] = "Invalid Enum"; -static const ALCchar alcErrInvalidValue[] = "Invalid Value"; -static const ALCchar alcErrOutOfMemory[] = "Out of Memory"; -static const ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " - "ALC_EXT_thread_local_context"; +[[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"; } -static const ALCint alcMajorVersion = 1; -static const ALCint alcMinorVersion = 1; +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " + "ALC_EXT_thread_local_context"sv; +} + +constexpr ALCint alcMajorVersion = 1; +constexpr ALCint alcMinorVersion = 1; -static std::recursive_mutex EnumerationLock; -static std::mutex ContextSwitchLock; +std::recursive_mutex EnumerationLock; +std::mutex ContextSwitchLock; -static std::atomic LastError{ALC_NO_ERROR}; -static PtrIntMap DeviceIfaceMap; -static PtrIntMap ContextIfaceMap; +std::atomic LastError{ALC_NO_ERROR}; +std::unordered_map DeviceIfaceMap; +std::unordered_map ContextIfaceMap; + +template +auto maybe_get(std::unordered_map &list, V&& key) -> std::optional +{ + auto iter = list.find(std::forward(key)); + if(iter != list.end()) return iter->second; + return std::nullopt; +} -typedef struct EnumeratedList { +struct EnumeratedList { std::vector Names; - std::vector Indicies; + std::vector Indicies; void clear() { Names.clear(); Indicies.clear(); } -} EnumeratedList; -static EnumeratedList DevicesList; -static EnumeratedList AllDevicesList; -static EnumeratedList CaptureDevicesList; -static void AppendDeviceList(EnumeratedList *list, const ALCchar *names, ALint idx) + void AppendDeviceList(const ALCchar *names, ALCuint idx); + [[nodiscard]] + auto GetDriverIndexForName(const std::string_view name) const -> std::optional; +}; +EnumeratedList DevicesList; +EnumeratedList AllDevicesList; +EnumeratedList CaptureDevicesList; + +void EnumeratedList::AppendDeviceList(const ALCchar* names, ALCuint idx) { const ALCchar *name_end = names; if(!name_end) return; - ALCsizei count = 0; + size_t count{0}; while(*name_end) { - TRACE("Enumerated \"%s\", driver %d\n", name_end, idx); + TRACE("Enumerated \"{}\", driver {}", name_end, idx); ++count; - name_end += strlen(name_end)+1; + name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } if(names == name_end) return; - list->Names.reserve(list->Names.size() + (name_end - names) + 1); - list->Names.insert(list->Names.cend(), names, name_end); + Names.reserve(Names.size() + static_cast(name_end - names) + 1); + Names.insert(Names.cend(), names, name_end); - list->Indicies.reserve(list->Indicies.size() + count); - list->Indicies.insert(list->Indicies.cend(), count, idx); + Indicies.reserve(Indicies.size() + count); + Indicies.insert(Indicies.cend(), count, idx); } -static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *name) +auto EnumeratedList::GetDriverIndexForName(const std::string_view name) const -> std::optional { - const ALCchar *devnames = list->Names.data(); - const ALCint *index = list->Indicies.data(); + auto devnames = Names.cbegin(); + auto index = Indicies.cbegin(); - while(devnames && *devnames) + while(devnames != Names.cend() && *devnames) { - if(strcmp(name, devnames) == 0) - return *index; - devnames += strlen(devnames)+1; - index++; + const auto devname = std::string_view{al::to_address(devnames)}; + if(name == devname) return *index; + + devnames += ptrdiff_t(devname.size()+1); + ++index; } - return -1; + return std::nullopt; } -static void InitCtxFuncs(DriverIface &iface) +void InitCtxFuncs(DriverIface &iface) { ALCdevice *device{iface.alcGetContextsDevice(iface.alcGetCurrentContext())}; #define LOAD_PROC(x) do { \ iface.x = reinterpret_cast(iface.alGetProcAddress(#x));\ if(!iface.x) \ - ERR("Failed to find entry point for %s in %ls\n", #x, \ - iface.Name.c_str()); \ + ERR("Failed to find entry point for {} in {}", #x, \ + wstr_to_utf8(iface.Name)); \ } while(0) if(iface.alcIsExtensionPresent(device, "ALC_EXT_EFX")) { @@ -394,65 +421,69 @@ static void InitCtxFuncs(DriverIface &iface) #undef LOAD_PROC } +} /* namespace */ + ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + LoadDrivers(); + + ALCdevice *device{nullptr}; + std::optional idx; /* Prior to the enumeration extension, apps would hardcode these names as a * quality hint for the wrapper driver. Ignore them since there's no sane * way to map them. */ - if(devicename && (devicename[0] == '\0' || - strcmp(devicename, "DirectSound3D") == 0 || - strcmp(devicename, "DirectSound") == 0 || - strcmp(devicename, "MMSYSTEM") == 0)) - devicename = nullptr; - if(devicename) + if(devicename && *devicename != '\0' && devicename != "DirectSound3D"sv + && devicename != "DirectSound"sv && devicename != "MMSYSTEM"sv) { { std::lock_guard enumlock{EnumerationLock}; if(DevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&DevicesList, devicename); - if(idx < 0) + idx = DevicesList.GetDriverIndexForName(devicename); + if(!idx) { if(AllDevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); - idx = GetDriverIndexForName(&AllDevicesList, devicename); + idx = AllDevicesList.GetDriverIndexForName(devicename); } } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); - TRACE("Failed to find driver for name \"%s\"\n", devicename); + TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcOpenDevice(devicename); + TRACE("Found driver {} for name \"{}\"", *idx, devicename); + device = DriverList[*idx]->alcOpenDevice(devicename); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - TRACE("Using default device from driver %d\n", idx); + TRACE("Using default device from driver {}", drvidx); device = drv->alcOpenDevice(nullptr); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } @@ -462,36 +493,36 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcep ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) noexcept { - ALCcontext *context; - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + const auto idx = maybe_get(DeviceIfaceMap, device); + if(!idx) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } - context = DriverList[idx]->alcCreateContext(device, attrlist); + + ALCcontext *context{DriverList[*idx]->alcCreateContext(device, attrlist)}; if(context) { - if(ContextIfaceMap.insert(context, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcDestroyContext(context); + try { + ContextIfaceMap.emplace(context, *idx); + } + catch(...) { + DriverList[*idx]->alcDestroyContext(context); context = nullptr; } } @@ -501,41 +532,40 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { - ALint idx = -1; - std::lock_guard ctxlock{ContextSwitchLock}; + + std::optional idx; if(context) { - idx = ContextIfaceMap.lookupByKey(context); - if(idx < 0) + idx = maybe_get(ContextIfaceMap, context); + if(!idx) { LastError.store(ALC_INVALID_CONTEXT); return ALC_FALSE; } - if(!DriverList[idx]->alcMakeContextCurrent(context)) + if(!DriverList[*idx]->alcMakeContextCurrent(context)) return ALC_FALSE; - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + 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 < 0) + if(!idx) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { - DriverIface *oldiface = GetThreadDriver(); - if(oldiface && oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcSetThreadContext(nullptr); - oldiface = CurrentCtxDriver.exchange(DriverList[idx].get()); - if(oldiface && oldiface != DriverList[idx].get()) + oldiface = CurrentCtxDriver.exchange(DriverList[*idx].get()); + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); @@ -545,55 +575,43 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexc ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcProcessContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcProcessContext(context); + LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcSuspendContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcSuspendContext(context); + LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { - ALint idx; - - if(!context || (idx=ContextIfaceMap.lookupByKey(context)) < 0) + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - LastError.store(ALC_INVALID_CONTEXT); + DriverList[*idx]->alcDestroyContext(context); + ContextIfaceMap.erase(context); return; } - - DriverList[idx]->alcDestroyContext(context); - ContextIfaceMap.removeByKey(context); + LastError.store(ALC_INVALID_CONTEXT); } ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext() noexcept { - DriverIface *iface = GetThreadDriver(); + DriverIface *iface{GetThreadDriver()}; if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcGetContextsDevice(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcGetContextsDevice(context); + LastError.store(ALC_INVALID_CONTEXT); return nullptr; } @@ -603,41 +621,34 @@ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) return ALC_INVALID_DEVICE; - return DriverList[idx]->alcGetError(device); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetError(device); + return ALC_INVALID_DEVICE; } return LastError.exchange(ALC_NO_ERROR); } ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) noexcept { - const char *ptr; - size_t len; - if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; - } - return DriverList[idx]->alcIsExtensionPresent(device, extname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcIsExtensionPresent(device, extname); + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } - len = strlen(extname); - ptr = alcExtensionList; - while(ptr && *ptr) + const auto tofind = std::string_view{extname}; + const auto extlist = GetExtensionList(); + auto matchpos = extlist.find(tofind); + while(matchpos != std::string_view::npos) { - if(al::strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + const auto endpos = matchpos + tofind.size(); + if((matchpos == 0 || std::isspace(extlist[matchpos-1])) + && (endpos == extlist.size() || std::isspace(extlist[endpos]))) return ALC_TRUE; - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } + matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } @@ -646,13 +657,11 @@ ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *f { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetProcAddress(device, funcname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetProcAddress(device, funcname); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), @@ -666,13 +675,11 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return 0; - } - return DriverList[idx]->alcGetEnumValue(device, enumname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetEnumValue(device, enumname); + + LastError.store(ALC_INVALID_DEVICE); + return 0; } auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), @@ -684,46 +691,38 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) noexcept { + LoadDrivers(); + if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetString(device, param); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetString(device, param); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } switch(param) { - case ALC_NO_ERROR: - return alcNoError; - case ALC_INVALID_ENUM: - return alcErrInvalidEnum; - case ALC_INVALID_VALUE: - return alcErrInvalidValue; - case ALC_INVALID_DEVICE: - return alcErrInvalidDevice; - case ALC_INVALID_CONTEXT: - return alcErrInvalidContext; - case ALC_OUT_OF_MEMORY: - return alcErrOutOfMemory; - case ALC_EXTENSIONS: - return alcExtensionList; + case ALC_NO_ERROR: return GetNoErrorString(); + case ALC_INVALID_ENUM: return GetInvalidEnumString(); + case ALC_INVALID_VALUE: return GetInvalidValueString(); + case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); + case ALC_INVALID_CONTEXT: return GetInvalidContextString(); + case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); + case ALC_EXTENSIONS: return GetExtensionList().data(); case ALC_DEVICE_SPECIFIER: { std::lock_guard enumlock{EnumerationLock}; DevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&DevicesList, - drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + DevicesList.AppendDeviceList(drv->alcGetString(nullptr,ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -737,18 +736,18 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { std::lock_guard enumlock{EnumerationLock}; AllDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute * standard enumeration. */ if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); - else if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + else if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } @@ -763,12 +762,12 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { std::lock_guard enumlock{EnumerationLock}; CaptureDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) - AppendDeviceList(&CaptureDevicesList, + CaptureDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } @@ -783,7 +782,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) return drv->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); } @@ -804,7 +803,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para { for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) return drv->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); } @@ -822,13 +821,11 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return; - } - return DriverList[idx]->alcGetIntegerv(device, param, size, values); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetIntegerv(device, param, size, values); + + LastError.store(ALC_INVALID_DEVICE); + return; } if(size <= 0 || values == nullptr) @@ -842,14 +839,15 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi case ALC_MAJOR_VERSION: if(size >= 1) { - values[0] = alcMajorVersion; + *values = alcMajorVersion; return; } - /*fall-through*/ + LastError.store(ALC_INVALID_VALUE); + return; case ALC_MINOR_VERSION: if(size >= 1) { - values[0] = alcMinorVersion; + *values = alcMinorVersion; return; } LastError.store(ALC_INVALID_VALUE); @@ -873,51 +871,56 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi } -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) noexcept +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, + ALCenum format, ALCsizei buffersize) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + LoadDrivers(); - if(devicename && devicename[0] == '\0') - devicename = nullptr; - if(devicename) + ALCdevice *device{nullptr}; + std::optional idx; + + if(devicename && *devicename != '\0') { { std::lock_guard enumlock{EnumerationLock}; if(CaptureDevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&CaptureDevicesList, devicename); + idx = CaptureDevicesList.GetDriverIndexForName(devicename); } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); - TRACE("Failed to find driver for name \"%s\"\n", devicename); + TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); + TRACE("Found driver {} for name \"{}\"", *idx, devicename); + device = DriverList[*idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { - if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { - TRACE("Using default capture device from driver %d\n", idx); + TRACE("Using default capture device from driver {}", drvidx); device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCaptureCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCaptureCloseDevice(device); device = nullptr; } } @@ -927,84 +930,67 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCaptureCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCaptureCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStart(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStart(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStop(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStop(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureSamples(device, buffer, samples); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureSamples(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { - ALCenum err = ALC_INVALID_CONTEXT; - ALint idx; - if(!context) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; SetThreadDriver(nullptr); return ALC_TRUE; } - idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) + ALCenum err{ALC_INVALID_CONTEXT}; + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - if(DriverList[idx]->alcSetThreadContext(context)) + if(DriverList[*idx]->alcSetThreadContext(context)) { - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{InitCtxFuncs(*DriverList[*idx]);}); - DriverIface *oldiface = GetThreadDriver(); - if(oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface != DriverList[*idx].get()) { - SetThreadDriver(DriverList[idx].get()); + SetThreadDriver(DriverList[*idx].get()); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; } - err = DriverList[idx]->alcGetError(nullptr); + err = DriverList[*idx]->alcGetError(nullptr); } LastError.store(err); return ALC_FALSE; @@ -1012,7 +998,7 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcep ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext() noexcept { - DriverIface *iface = GetThreadDriver(); - if(iface) return iface->alcGetThreadContext(); + if(DriverIface *iface{GetThreadDriver()}) + return iface->alcGetThreadContext(); return nullptr; } diff --git a/libs/openal-soft/router/router.cpp b/libs/openal-soft/router/router.cpp index 1fdf45ac..d05d48e5 100644 --- a/libs/openal-soft/router/router.cpp +++ b/libs/openal-soft/router/router.cpp @@ -4,105 +4,94 @@ #include "router.h" #include +#include #include #include #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" -#include "almalloc.h" +#include "albit.h" +#include "alstring.h" +#include "opthelpers.h" #include "strutils.h" #include "version.h" -enum LogLevel LogLevel = LogLevel_Error; -FILE *LogFile; +eLogLevel LogLevel{eLogLevel::Error}; +gsl::owner LogFile; -static void LoadDriverList(); +namespace { + +std::vector gAcceptList; +std::vector gRejectList; -BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) -{ - switch(reason) - { - case DLL_PROCESS_ATTACH: - LogFile = stderr; - if(auto logfname = al::getenv("ALROUTER_LOGFILE")) - { - FILE *f = fopen(logfname->c_str(), "w"); - if(f == nullptr) - ERR("Could not open log file: %s\n", logfname->c_str()); - else - LogFile = f; - } - if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) - { - char *end = nullptr; - long l = strtol(loglev->c_str(), &end, 0); - if(!end || *end != '\0') - ERR("Invalid log level value: %s\n", loglev->c_str()); - else if(l < LogLevel_None || l > LogLevel_Trace) - ERR("Log level out of range: %s\n", loglev->c_str()); - else - LogLevel = static_cast(l); - } - TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - LoadDriverList(); - - break; - - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - DriverList.clear(); - - if(LogFile && LogFile != stderr) - fclose(LogFile); - LogFile = nullptr; - - break; - } - return TRUE; -} - - -static void AddModule(HMODULE module, const WCHAR *name) +void AddModule(HMODULE module, const std::wstring_view name) { for(auto &drv : DriverList) { if(drv->Module == module) { - TRACE("Skipping already-loaded module %p\n", decltype(std::declval()){module}); + TRACE("Skipping already-loaded module {}", decltype(std::declval()){module}); FreeLibrary(module); return; } if(drv->Name == name) { - TRACE("Skipping similarly-named module %ls\n", name); + TRACE("Skipping similarly-named module {}", wstr_to_utf8(name)); + FreeLibrary(module); + return; + } + } + if(!gAcceptList.empty()) + { + auto iter = std::find_if(gAcceptList.cbegin(), gAcceptList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter == gAcceptList.cend()) + { + TRACE("{} not found in ALROUTER_ACCEPT, skipping", wstr_to_utf8(name)); + FreeLibrary(module); + return; + } + } + if(!gRejectList.empty()) + { + auto iter = std::find_if(gRejectList.cbegin(), gRejectList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter != gRejectList.cend()) + { + TRACE("{} found in ALROUTER_REJECT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } } - DriverList.emplace_back(std::make_unique(name, module)); - DriverIface &newdrv = *DriverList.back(); + DriverIface &newdrv = *DriverList.emplace_back(std::make_unique(name, module)); /* Load required functions. */ - int err = 0; -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + bool loadok{true}; + auto do_load = [module,name](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + { + ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); + return false; + } + + func = al::bit_cast(ptr); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load(newdrv.x, #x) LOAD_PROC(alcCreateContext); LOAD_PROC(alcMakeContextCurrent); LOAD_PROC(alcProcessContext); @@ -185,262 +174,284 @@ static void AddModule(HMODULE module, const WCHAR *name) LOAD_PROC(alDopplerVelocity); LOAD_PROC(alSpeedOfSound); LOAD_PROC(alDistanceModel); - if(!err) +#undef LOAD_PROC + if(loadok) { - ALCint alc_ver[2] = { 0, 0 }; + std::array alc_ver{0, 0}; newdrv.alcGetIntegerv(nullptr, ALC_MAJOR_VERSION, 1, &alc_ver[0]); newdrv.alcGetIntegerv(nullptr, ALC_MINOR_VERSION, 1, &alc_ver[1]); if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) - newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]); + newdrv.ALCVer = MakeALCVer(alc_ver[0], alc_ver[1]); else { - WARN("Failed to query ALC version for %ls, assuming 1.0\n", name); - newdrv.ALCVer = MAKE_ALC_VER(1, 0); + WARN("Failed to query ALC version for {}, assuming 1.0", wstr_to_utf8(name)); + newdrv.ALCVer = MakeALCVer(1, 0); } + auto do_load2 = [module,name](auto &func, const char *fname) -> void + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + WARN("Failed to find optional entry point for {} in {}", fname, + wstr_to_utf8(name)); + else + func = al::bit_cast(ptr); + }; +#define LOAD_PROC(x) do_load2(newdrv.x, #x) + LOAD_PROC(alBufferf); + LOAD_PROC(alBuffer3f); + LOAD_PROC(alBufferfv); + LOAD_PROC(alBufferi); + LOAD_PROC(alBuffer3i); + LOAD_PROC(alBufferiv); + LOAD_PROC(alGetBufferf); + LOAD_PROC(alGetBuffer3f); + LOAD_PROC(alGetBufferfv); + LOAD_PROC(alGetBufferi); + LOAD_PROC(alGetBuffer3i); + LOAD_PROC(alGetBufferiv); #undef LOAD_PROC -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - WARN("Failed to find optional entry point for %s in %ls\n", #x, name); \ - } \ -} while(0) - 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 -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast( \ - newdrv.alcGetProcAddress(nullptr, #x)); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + auto do_load3 = [name,&newdrv](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = newdrv.alcGetProcAddress(nullptr, fname); + if(!ptr) + { + ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); + return false; + } + + func = reinterpret_cast(ptr); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load3(newdrv.x, #x) if(newdrv.alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context")) { LOAD_PROC(alcSetThreadContext); LOAD_PROC(alcGetThreadContext); } +#undef LOAD_PROC } - if(err) + if(!loadok) { DriverList.pop_back(); return; } - TRACE("Loaded module %p, %ls, ALC %d.%d\n", decltype(std::declval()){module}, name, - newdrv.ALCVer>>8, newdrv.ALCVer&255); -#undef LOAD_PROC + TRACE("Loaded module {}, {}, ALC {}.{}", decltype(std::declval()){module}, + wstr_to_utf8(name), newdrv.ALCVer>>8, newdrv.ALCVer&255); } -static void SearchDrivers(WCHAR *path) +void SearchDrivers(const std::wstring_view path) { - WIN32_FIND_DATAW fdata; - - TRACE("Searching for drivers in %ls...\n", path); - std::wstring srchPath = path; + TRACE("Searching for drivers in {}...", wstr_to_utf8(path)); + std::wstring srchPath{path}; srchPath += L"\\*oal.dll"; - HANDLE srchHdl = FindFirstFileW(srchPath.c_str(), &fdata); - if(srchHdl != INVALID_HANDLE_VALUE) - { - do { - HMODULE mod; + WIN32_FIND_DATAW fdata{}; + HANDLE srchHdl{FindFirstFileW(srchPath.c_str(), &fdata)}; + if(srchHdl == INVALID_HANDLE_VALUE) return; - srchPath = path; - srchPath += L"\\"; - srchPath += fdata.cFileName; - TRACE("Found %ls\n", srchPath.c_str()); + do { + srchPath = path; + srchPath += L"\\"; + srchPath += std::data(fdata.cFileName); + TRACE("Found {}", wstr_to_utf8(srchPath)); - mod = LoadLibraryW(srchPath.c_str()); - if(!mod) - WARN("Could not load %ls\n", srchPath.c_str()); - else - AddModule(mod, fdata.cFileName); - } while(FindNextFileW(srchHdl, &fdata)); - FindClose(srchHdl); - } + HMODULE mod{LoadLibraryW(srchPath.c_str())}; + if(!mod) + WARN("Could not load {}", wstr_to_utf8(srchPath)); + else + AddModule(mod, std::data(fdata.cFileName)); + } while(FindNextFileW(srchHdl, &fdata)); + FindClose(srchHdl); } -static WCHAR *strrchrW(WCHAR *str, WCHAR ch) +bool GetLoadedModuleDirectory(const WCHAR *name, std::wstring *moddir) { - WCHAR *res = nullptr; - while(str && *str != '\0') - { - if(*str == ch) - res = str; - ++str; - } - return res; -} - -static int GetLoadedModuleDirectory(const WCHAR *name, WCHAR *moddir, DWORD length) -{ - HMODULE module = nullptr; - WCHAR *sep0, *sep1; + HMODULE module{nullptr}; if(name) { module = GetModuleHandleW(name); - if(!module) return 0; + if(!module) return false; } - if(GetModuleFileNameW(module, moddir, length) == 0) - return 0; + moddir->assign(256, '\0'); + DWORD res{GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size()))}; + if(res >= moddir->size()) + { + do { + moddir->append(256, '\0'); + res = GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size())); + } while(res >= moddir->size()); + } + moddir->resize(res); - sep0 = strrchrW(moddir, '/'); - if(sep0) sep1 = strrchrW(sep0+1, '\\'); - else sep1 = strrchrW(moddir, '\\'); + auto sep0 = moddir->rfind('/'); + auto sep1 = moddir->rfind('\\'); + if(sep0 < moddir->size() && sep1 < moddir->size()) + moddir->resize(std::max(sep0, sep1)); + else if(sep0 < moddir->size()) + moddir->resize(sep0); + else if(sep1 < moddir->size()) + moddir->resize(sep1); + else + moddir->resize(0); - if(sep1) *sep1 = '\0'; - else if(sep0) *sep0 = '\0'; - else *moddir = '\0'; - - return 1; + return !moddir->empty(); } +} // namespace + void LoadDriverList() { - WCHAR dll_path[MAX_PATH+1] = L""; - WCHAR cwd_path[MAX_PATH+1] = L""; - WCHAR proc_path[MAX_PATH+1] = L""; - WCHAR sys_path[MAX_PATH+1] = L""; + TRACE("Initializing router v0.1-{} {}", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - if(GetLoadedModuleDirectory(L"OpenAL32.dll", dll_path, MAX_PATH)) - TRACE("Got DLL path %ls\n", dll_path); + if(auto list = al::getenv(L"ALROUTER_ACCEPT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gAcceptList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } + if(auto list = al::getenv(L"ALROUTER_REJECT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gRejectList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } - GetCurrentDirectoryW(MAX_PATH, cwd_path); - auto len = wcslen(cwd_path); - if(len > 0 && (cwd_path[len-1] == '\\' || cwd_path[len-1] == '/')) - cwd_path[len-1] = '\0'; - TRACE("Got current working directory %ls\n", cwd_path); + std::wstring dll_path; + if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) + TRACE("Got DLL path {}", wstr_to_utf8(dll_path)); - if(GetLoadedModuleDirectory(nullptr, proc_path, MAX_PATH)) - TRACE("Got proc path %ls\n", proc_path); + std::wstring cwd_path; + if(DWORD pathlen{GetCurrentDirectoryW(0, nullptr)}) + { + do { + cwd_path.resize(pathlen); + pathlen = GetCurrentDirectoryW(pathlen, cwd_path.data()); + } while(pathlen >= cwd_path.size()); + cwd_path.resize(pathlen); + } + if(!cwd_path.empty() && (cwd_path.back() == '\\' || cwd_path.back() == '/')) + cwd_path.pop_back(); + if(!cwd_path.empty()) + TRACE("Got current working directory {}", wstr_to_utf8(cwd_path)); - GetSystemDirectoryW(sys_path, MAX_PATH); - len = wcslen(sys_path); - if(len > 0 && (sys_path[len-1] == '\\' || sys_path[len-1] == '/')) - sys_path[len-1] = '\0'; - TRACE("Got system path %ls\n", sys_path); + std::wstring proc_path; + if(GetLoadedModuleDirectory(nullptr, &proc_path)) + TRACE("Got proc path {}", wstr_to_utf8(proc_path)); + + std::wstring sys_path; + if(UINT pathlen{GetSystemDirectoryW(nullptr, 0)}) + { + do { + sys_path.resize(pathlen); + pathlen = GetSystemDirectoryW(sys_path.data(), pathlen); + } while(pathlen >= sys_path.size()); + sys_path.resize(pathlen); + } + if(!sys_path.empty() && (sys_path.back() == '\\' || sys_path.back() == '/')) + sys_path.pop_back(); + if(!sys_path.empty()) + TRACE("Got system path {}", wstr_to_utf8(sys_path)); /* Don't search the DLL's path if it is the same as the current working * directory, app's path, or system path (don't want to do duplicate * searches, or increase the priority of the app or system path). */ - if(dll_path[0] && - (!cwd_path[0] || wcscmp(dll_path, cwd_path) != 0) && - (!proc_path[0] || wcscmp(dll_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(dll_path, sys_path) != 0)) + 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[0] && - (!proc_path[0] || wcscmp(cwd_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(cwd_path, sys_path) != 0)) + if(!cwd_path.empty() && (proc_path.empty() || cwd_path != proc_path) + && (sys_path.empty() || cwd_path != sys_path)) SearchDrivers(cwd_path); - if(proc_path[0] && (!sys_path[0] || wcscmp(proc_path, sys_path) != 0)) + if(!proc_path.empty() && (sys_path.empty() || proc_path != sys_path)) SearchDrivers(proc_path); - if(sys_path[0]) + if(!sys_path.empty()) SearchDrivers(sys_path); -} - -PtrIntMap::~PtrIntMap() -{ - std::lock_guard maplock{mLock}; - free(mKeys); - mKeys = nullptr; - mValues = nullptr; - mSize = 0; - mCapacity = 0; -} - -ALenum PtrIntMap::insert(void *key, int value) -{ - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - - if(pos == mSize || mKeys[pos] != key) + /* Sort drivers that can enumerate device names to the front. */ + static constexpr auto is_enumerable = [](DriverIfacePtr &drv) { - if(mSize == mCapacity) - { - void **newkeys{nullptr}; - ALsizei newcap{mCapacity ? (mCapacity<<1) : 4}; - if(newcap > mCapacity) - newkeys = static_cast( - calloc(newcap, sizeof(mKeys[0])+sizeof(mValues[0]))); - if(!newkeys) - return AL_OUT_OF_MEMORY; - auto newvalues = reinterpret_cast(&newkeys[newcap]); + return drv->ALCVer >= MakeALCVer(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); + }; + std::stable_partition(DriverList.begin(), DriverList.end(), is_enumerable); - if(mKeys) - { - std::copy_n(mKeys, mSize, newkeys); - std::copy_n(mValues, mSize, newvalues); - } - free(mKeys); - mKeys = newkeys; - mValues = newvalues; - mCapacity = newcap; - } - - if(pos < mSize) - { - std::copy_backward(mKeys+pos, mKeys+mSize, mKeys+mSize+1); - std::copy_backward(mValues+pos, mValues+mSize, mValues+mSize+1); - } - mSize++; - } - mKeys[pos] = key; - mValues[pos] = value; - - return AL_NO_ERROR; + /* 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)); } -int PtrIntMap::removeByKey(void *key) +BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) + switch(reason) { - ret = mValues[pos]; - if(pos+1 < mSize) + case DLL_PROCESS_ATTACH: + if(auto logfname = al::getenv(L"ALROUTER_LOGFILE")) { - std::copy(mKeys+pos+1, mKeys+mSize, mKeys+pos); - std::copy(mValues+pos+1, mValues+mSize, mValues+pos); + gsl::owner f{_wfopen(logfname->c_str(), L"w")}; + if(f == nullptr) + ERR("Could not open log file: {}", wstr_to_utf8(*logfname)); + else + LogFile = f; } - mSize--; + if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) + { + char *end = nullptr; + long l{strtol(loglev->c_str(), &end, 0)}; + if(!end || *end != '\0') + ERR("Invalid log level value: {}", *loglev); + else if(l < al::to_underlying(eLogLevel::None) + || l > al::to_underlying(eLogLevel::Trace)) + ERR("Log level out of range: {}", *loglev); + else + LogLevel = static_cast(l); + } + break; + + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + DriverList.clear(); + + if(LogFile) + fclose(LogFile); + LogFile = nullptr; + + break; } - - return ret; -} - -int PtrIntMap::lookupByKey(void *key) -{ - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) - ret = mValues[pos]; - - return ret; + return TRUE; } diff --git a/libs/openal-soft/router/router.h b/libs/openal-soft/router/router.h index f5c7f455..f030fe69 100644 --- a/libs/openal-soft/router/router.h +++ b/libs/openal-soft/router/router.h @@ -5,9 +5,8 @@ #include #include -#include - #include +#include #include #include #include @@ -18,15 +17,13 @@ #include "AL/al.h" #include "AL/alext.h" +#include "almalloc.h" +#include "fmt/core.h" -#define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor)) + +constexpr auto MakeALCVer(int major, int minor) noexcept -> int { return (major<<8) | minor; } struct DriverIface { - std::wstring Name; - HMODULE Module{nullptr}; - int ALCVer{0}; - std::once_flag InitOnceCtx{}; - LPALCCREATECONTEXT alcCreateContext{nullptr}; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr}; LPALCPROCESSCONTEXT alcProcessContext{nullptr}; @@ -160,16 +157,19 @@ struct DriverIface { 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); - Module = nullptr; - } + 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; @@ -182,54 +182,41 @@ inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } -class PtrIntMap { - void **mKeys{nullptr}; - /* Shares memory with keys. */ - int *mValues{nullptr}; - - ALsizei mSize{0}; - ALsizei mCapacity{0}; - std::mutex mLock; - -public: - PtrIntMap() = default; - ~PtrIntMap(); - - ALenum insert(void *key, int value); - int removeByKey(void *key); - int lookupByKey(void *key); +enum class eLogLevel { + None = 0, + Error = 1, + Warn = 2, + Trace = 3, }; +extern eLogLevel LogLevel; +extern gsl::owner LogFile; + +#define TRACE(...) do { \ + if(LogLevel >= eLogLevel::Trace) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (II): " __VA_ARGS__); \ + fflush(file); \ + } \ +} while(0) +#define WARN(...) do { \ + if(LogLevel >= eLogLevel::Warn) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (WW): " __VA_ARGS__); \ + fflush(file); \ + } \ +} while(0) +#define ERR(...) do { \ + if(LogLevel >= eLogLevel::Error) \ + { \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fmt::println(file, "AL Router (EE): " __VA_ARGS__); \ + fflush(file); \ + } \ +} while(0) -enum LogLevel { - LogLevel_None = 0, - LogLevel_Error = 1, - LogLevel_Warn = 2, - LogLevel_Trace = 3, -}; -extern enum LogLevel LogLevel; -extern FILE *LogFile; - -#define TRACE(...) do { \ - if(LogLevel >= LogLevel_Trace) \ - { \ - fprintf(LogFile, "AL Router (II): " __VA_ARGS__); \ - fflush(LogFile); \ - } \ -} while(0) -#define WARN(...) do { \ - if(LogLevel >= LogLevel_Warn) \ - { \ - fprintf(LogFile, "AL Router (WW): " __VA_ARGS__); \ - fflush(LogFile); \ - } \ -} while(0) -#define ERR(...) do { \ - if(LogLevel >= LogLevel_Error) \ - { \ - fprintf(LogFile, "AL Router (EE): " __VA_ARGS__); \ - fflush(LogFile); \ - } \ -} while(0) +void LoadDriverList(); #endif /* ROUTER_ROUTER_H */ diff --git a/libs/openal-soft/utils/alsoft-config/CMakeLists.txt b/libs/openal-soft/utils/alsoft-config/CMakeLists.txt index d9e6ca9a..62d6e10d 100644 --- a/libs/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/libs/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -12,13 +12,14 @@ if(Qt5Widgets_FOUND) verstr.cpp verstr.h ${UIS} ${RSCS} ${TRS} ${MOCS}) - target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alcommon) + target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alsoft.common) target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" "${OpenAL_BINARY_DIR}") + target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS) set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) - if(TARGET build_version) - add_dependencies(alsoft-config build_version) + if(TARGET alsoft.build_version) + add_dependencies(alsoft-config alsoft.build_version) endif() message(STATUS "Building configuration program") diff --git a/libs/openal-soft/utils/alsoft-config/mainwindow.cpp b/libs/openal-soft/utils/alsoft-config/mainwindow.cpp index ead96e08..14b396d5 100644 --- a/libs/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/libs/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -1,11 +1,13 @@ #include "config.h" +#include "config_backends.h" +#include "config_simd.h" #include "mainwindow.h" #include #include -#include +#include #include #include @@ -32,48 +34,48 @@ struct BackendNamePair { /* NOLINTEND(*-avoid-c-arrays) */ }; constexpr std::array backendList{ -#ifdef HAVE_PIPEWIRE +#if HAVE_PIPEWIRE BackendNamePair{ "pipewire", "PipeWire" }, #endif -#ifdef HAVE_PULSEAUDIO +#if HAVE_PULSEAUDIO BackendNamePair{ "pulse", "PulseAudio" }, #endif -#ifdef HAVE_ALSA - BackendNamePair{ "alsa", "ALSA" }, -#endif -#ifdef HAVE_JACK - BackendNamePair{ "jack", "JACK" }, -#endif -#ifdef HAVE_COREAUDIO - BackendNamePair{ "core", "CoreAudio" }, -#endif -#ifdef HAVE_OSS - BackendNamePair{ "oss", "OSS" }, -#endif -#ifdef HAVE_SOLARIS - BackendNamePair{ "solaris", "Solaris" }, -#endif -#ifdef HAVE_SNDIO - BackendNamePair{ "sndio", "SoundIO" }, -#endif -#ifdef HAVE_WASAPI +#if HAVE_WASAPI BackendNamePair{ "wasapi", "WASAPI" }, #endif -#ifdef HAVE_DSOUND +#if HAVE_COREAUDIO + BackendNamePair{ "core", "CoreAudio" }, +#endif +#if HAVE_OPENSL + BackendNamePair{ "opensl", "OpenSL" }, +#endif +#if HAVE_ALSA + BackendNamePair{ "alsa", "ALSA" }, +#endif +#if HAVE_SOLARIS + BackendNamePair{ "solaris", "Solaris" }, +#endif +#if HAVE_SNDIO + BackendNamePair{ "sndio", "SndIO" }, +#endif +#if HAVE_OSS + BackendNamePair{ "oss", "OSS" }, +#endif +#if HAVE_DSOUND BackendNamePair{ "dsound", "DirectSound" }, #endif -#ifdef HAVE_WINMM +#if HAVE_WINMM BackendNamePair{ "winmm", "Windows Multimedia" }, #endif -#ifdef HAVE_PORTAUDIO +#if HAVE_PORTAUDIO BackendNamePair{ "port", "PortAudio" }, #endif -#ifdef HAVE_OPENSL - BackendNamePair{ "opensl", "OpenSL" }, +#if HAVE_JACK + BackendNamePair{ "jack", "JACK" }, #endif BackendNamePair{ "null", "Null Output" }, -#ifdef HAVE_WAVE +#if HAVE_WAVE BackendNamePair{ "wave", "Wave Writer" }, #endif }; @@ -111,12 +113,15 @@ constexpr std::array sampleTypeList{ constexpr std::array resamplerList{ NameValuePair{ "Point", "point" }, NameValuePair{ "Linear", "linear" }, - NameValuePair{ "Cubic Spline", "cubic" }, + NameValuePair{ "Cubic Spline", "spline" }, NameValuePair{ "Default (Cubic Spline)", "" }, + NameValuePair{ "4-point Gaussian", "gaussian" }, NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" }, NameValuePair{ "11th order Sinc", "bsinc12" }, NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" }, NameValuePair{ "23rd order Sinc", "bsinc24" }, + NameValuePair{ "47th order Sinc (fast)", "fast_bsinc48" }, + NameValuePair{ "47th order Sinc", "bsinc48" }, }; constexpr std::array stereoModeList{ NameValuePair{ "Autodetect", "" }, @@ -144,19 +149,35 @@ constexpr std::array hrtfModeList{ NameValuePair{ "Full", "full" }, }; +constexpr auto GetDefaultIndex(const al::span list) -> size_t +{ + for(size_t i{0};i < list.size();++i) + { + if(!list[i].value[0]) + return i; + } + throw std::runtime_error{"Failed to find default entry"}; +} + +#ifdef Q_OS_WIN32 +struct CoTaskMemDeleter { + void operator()(void *buffer) { CoTaskMemFree(buffer); } +}; +/* NOLINTNEXTLINE(*-avoid-c-arrays) */ +using WCharBufferPtr = std::unique_ptr; +#endif + QString getDefaultConfigName() { #ifdef Q_OS_WIN32 const char *fname{"alsoft.ini"}; - auto get_appdata_path = []() noexcept -> QString + static constexpr auto get_appdata_path = []() -> QString { - QString ret; - WCHAR *buffer{}; + auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, - nullptr, &buffer)}; SUCCEEDED(hr)) - ret = QString::fromWCharArray(buffer); - CoTaskMemFree(buffer); - return ret; + nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) + return QString::fromWCharArray(buffer.get()); + return QString{}; }; QString base = get_appdata_path(); #else @@ -177,15 +198,13 @@ QString getDefaultConfigName() QString getBaseDataPath() { #ifdef Q_OS_WIN32 - auto get_appdata_path = []() noexcept -> QString + static constexpr auto get_appdata_path = []() -> QString { - QString ret; - WCHAR *buffer{}; + auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, - nullptr, &buffer)}; SUCCEEDED(hr)) - ret = QString::fromWCharArray(buffer); - CoTaskMemFree(buffer); - return ret; + nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) + return QString::fromWCharArray(buffer.get()); + return QString{}; }; QString base = get_appdata_path(); #else @@ -264,9 +283,9 @@ QString getCheckValue(const QCheckBox *checkbox) { const Qt::CheckState state{checkbox->checkState()}; if(state == Qt::Checked) - return QString{"true"}; + return QStringLiteral("true"); if(state == Qt::Unchecked) - return QString{"false"}; + return QStringLiteral("false"); return QString{}; } @@ -296,18 +315,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} ui->resamplerSlider->setRange(0, resamplerList.size()-1); ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1); -#if !defined(HAVE_NEON) && !defined(HAVE_SSE) +#if !HAVE_NEON && !HAVE_SSE ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60); #else ui->cpuExtDisabledLabel->setVisible(false); #endif -#ifndef HAVE_NEON +#if !HAVE_NEON -#ifndef HAVE_SSE4_1 -#ifndef HAVE_SSE3 -#ifndef HAVE_SSE2 -#ifndef HAVE_SSE +#if !HAVE_SSE4_1 +#if !HAVE_SSE3 +#if !HAVE_SSE2 +#if !HAVE_SSE ui->enableSSECheckBox->setVisible(false); #endif /* !SSE */ ui->enableSSE2CheckBox->setVisible(false); @@ -320,10 +339,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} #else /* !Neon */ -#ifndef HAVE_SSE4_1 -#ifndef HAVE_SSE3 -#ifndef HAVE_SSE2 -#ifndef HAVE_SSE +#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 */ @@ -336,7 +355,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} #endif -#ifndef ALSOFT_EAX +#if !ALSOFT_EAX ui->enableEaxCheck->setChecked(Qt::Unchecked); ui->enableEaxCheck->setEnabled(false); ui->enableEaxCheck->setVisible(false); @@ -486,7 +505,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} { QList items = ui->backendListWidget->findItems( std::data(backendList[i].full_string), Qt::MatchFixedString); - foreach(QListWidgetItem *item, items) + Q_FOREACH(QListWidgetItem *item, items) item->setHidden(false); } @@ -537,9 +556,9 @@ QStringList MainWindow::collectHrtfs() { QDir dir(ui->hrtfFileList->item(i)->text()); QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -567,14 +586,14 @@ QStringList MainWindow::collectHrtfs() if(ui->defaultHrtfPathsCheckBox->isChecked()) { - QStringList paths = getAllDataPaths("/openal/hrtf"); - foreach(const QString &name, paths) + QStringList paths = getAllDataPaths(QStringLiteral("/openal/hrtf")); + Q_FOREACH(const QString &name, paths) { QDir dir{name}; QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)}; - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -601,7 +620,7 @@ QStringList MainWindow::collectHrtfs() } #ifdef ALSOFT_EMBED_HRTF_DATA - ret.push_back("Built-In HRTF"); + ret.push_back(QStringLiteral("Built-In HRTF")); #endif } return ret; @@ -619,7 +638,7 @@ void MainWindow::loadConfig(const QString &fname) { QSettings settings{fname, QSettings::IniFormat}; - QString sampletype = settings.value("sample-type").toString(); + QString sampletype{settings.value(QStringLiteral("sample-type")).toString()}; ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { @@ -631,12 +650,12 @@ void MainWindow::loadConfig(const QString &fname) } } - QString channelconfig{settings.value("channels").toString()}; + QString channelconfig{settings.value(QStringLiteral("channels")).toString()}; ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { - if(channelconfig == "surround51rear") - channelconfig = "surround51"; + if(channelconfig == QStringLiteral("surround51rear")) + channelconfig = QStringLiteral("surround51"); QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { @@ -645,7 +664,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString srate{settings.value("frequency").toString()}; + QString srate{settings.value(QStringLiteral("frequency")).toString()}; if(srate.isEmpty()) ui->sampleRateCombo->setCurrentIndex(0); else @@ -655,23 +674,26 @@ void MainWindow::loadConfig(const QString &fname) } ui->srcCountLineEdit->clear(); - ui->srcCountLineEdit->insert(settings.value("sources").toString()); + ui->srcCountLineEdit->insert(settings.value(QStringLiteral("sources")).toString()); ui->effectSlotLineEdit->clear(); - ui->effectSlotLineEdit->insert(settings.value("slots").toString()); + ui->effectSlotLineEdit->insert(settings.value(QStringLiteral("slots")).toString()); ui->srcSendLineEdit->clear(); - ui->srcSendLineEdit->insert(settings.value("sends").toString()); + ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString()); - QString resampler = settings.value("resampler").toString().trimmed(); - ui->resamplerSlider->setValue(2); - ui->resamplerLabel->setText(std::data(resamplerList[2].name)); - /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic" - * as a fallback. + auto resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); + static constexpr auto defaultResamplerIndex = GetDefaultIndex(resamplerList); + ui->resamplerSlider->setValue(defaultResamplerIndex); + ui->resamplerLabel->setText(std::data(resamplerList[defaultResamplerIndex].name)); + /* "Cubic" is an alias for the 4-point spline resampler. The "sinc4" and + * "sinc8" resamplers are unsupported, use "gaussian" as a fallback. */ - if(resampler == "sinc4" || resampler == "sinc8") - resampler = "cubic"; + 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 == "bsinc") - resampler = "bsinc12"; + else if(resampler == QLatin1String{"bsinc"}) + resampler = QStringLiteral("bsinc12"); for(int i = 0;resamplerList[i].name[0];i++) { if(resampler == std::data(resamplerList[i].value)) @@ -682,7 +704,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString stereomode = settings.value("stereo-mode").toString().trimmed(); + QString stereomode{settings.value(QStringLiteral("stereo-mode")).toString().trimmed()}; ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { @@ -710,10 +732,10 @@ void MainWindow::loadConfig(const QString &fname) updatePeriodCountSlider(); } - ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter"))); - ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither"))); + ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("output-limiter")))); + ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("dither")))); - QString stereopan{settings.value("stereo-encoding").toString()}; + QString stereopan{settings.value(QStringLiteral("stereo-encoding")).toString()}; ui->stereoEncodingComboBox->setCurrentIndex(0); if(stereopan.isEmpty() == false) { @@ -725,7 +747,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString ambiformat{settings.value("ambi-format").toString()}; + QString ambiformat{settings.value(QStringLiteral("ambi-format")).toString()}; ui->ambiFormatComboBox->setCurrentIndex(0); if(ambiformat.isEmpty() == false) { @@ -737,35 +759,36 @@ void MainWindow::loadConfig(const QString &fname) } } - ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode"))); - ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp"))); - ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc"))); - double speakerdist{settings.value("decoder/speaker-dist", 1.0).toDouble()}; + ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value(QStringLiteral("decoder/hq-mode")))); + ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/distance-comp")))); + ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/nfc")))); + double speakerdist{settings.value(QStringLiteral("decoder/speaker-dist"), 1.0).toDouble()}; ui->decoderSpeakerDistSpinBox->setValue(speakerdist); - ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString()); - ui->decoder51LineEdit->setText(settings.value("decoder/surround51").toString()); - ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString()); - ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString()); - ui->decoder3D71LineEdit->setText(settings.value("decoder/surround3d71").toString()); + ui->decoderQuadLineEdit->setText(settings.value(QStringLiteral("decoder/quad")).toString()); + ui->decoder51LineEdit->setText(settings.value(QStringLiteral("decoder/surround51")).toString()); + ui->decoder61LineEdit->setText(settings.value(QStringLiteral("decoder/surround61")).toString()); + ui->decoder71LineEdit->setText(settings.value(QStringLiteral("decoder/surround71")).toString()); + ui->decoder3D71LineEdit->setText(settings.value(QStringLiteral("decoder/surround3d71")).toString()); - QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()}; + 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("sse", Qt::CaseInsensitive)); - ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive)); - ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive)); - ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive)); - ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive)); + 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)); - QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()}; - ui->hrtfmodeSlider->setValue(2); - ui->hrtfmodeLabel->setText(std::data(hrtfModeList[3].name)); + auto hrtfmode = settings.value(QStringLiteral("hrtf-mode")).toString().trimmed(); + static constexpr auto defaultHrtfModeIndex = GetDefaultIndex(hrtfModeList); + ui->hrtfmodeSlider->setValue(defaultHrtfModeIndex); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[defaultHrtfModeIndex].name)); /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ - if(hrtfmode == "basic") - hrtfmode = "ambi2"; + if(hrtfmode == QLatin1String{"basic"}) + hrtfmode = QStringLiteral("ambi2"); for(size_t i{0};i < hrtfModeList.size();++i) { if(hrtfmode == std::data(hrtfModeList[i].value)) @@ -776,7 +799,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()}; + 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) @@ -794,15 +817,15 @@ void MainWindow::loadConfig(const QString &fname) updateHrtfRemoveButton(); ui->preferredHrtfComboBox->clear(); - ui->preferredHrtfComboBox->addItem("- Any -"); + ui->preferredHrtfComboBox->addItem(QStringLiteral("- Any -")); if(ui->defaultHrtfPathsCheckBox->isChecked()) { QStringList hrtfs{collectHrtfs()}; - foreach(const QString &name, hrtfs) + Q_FOREACH(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } - QString defaulthrtf{settings.value("default-hrtf").toString()}; + QString defaulthrtf{settings.value(QStringLiteral("default-hrtf")).toString()}; ui->preferredHrtfComboBox->setCurrentIndex(0); if(defaulthrtf.isEmpty() == false) { @@ -820,7 +843,7 @@ void MainWindow::loadConfig(const QString &fname) ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); - QStringList drivers{settings.value("drivers").toStringList()}; + QStringList drivers{settings.value(QStringLiteral("drivers")).toStringList()}; if(drivers.empty()) ui->backendCheckBox->setChecked(true); else @@ -833,14 +856,14 @@ void MainWindow::loadConfig(const QString &fname) /* Convert "mmdevapi" references to "wasapi" for backwards * compatibility. */ - if(name == "-mmdevapi") - name = "-wasapi"; - else if(name == "mmdevapi") - name = "wasapi"; + if(name == QLatin1String{"-mmdevapi"}) + name = QStringLiteral("-wasapi"); + else if(name == QLatin1String{"mmdevapi"}) + name = QStringLiteral("wasapi"); } - bool lastWasEmpty = false; - foreach(const QString &backend, drivers) + bool lastWasEmpty{false}; + Q_FOREACH(const QString &backend, drivers) { lastWasEmpty = backend.isEmpty(); if(lastWasEmpty) continue; @@ -872,7 +895,7 @@ void MainWindow::loadConfig(const QString &fname) ui->backendCheckBox->setChecked(lastWasEmpty); } - QString defaultreverb{settings.value("default-reverb").toString().toLower()}; + QString defaultreverb{settings.value(QStringLiteral("default-reverb")).toString().toLower()}; ui->defaultReverbComboBox->setCurrentIndex(0); if(defaultreverb.isEmpty() == false) { @@ -886,56 +909,56 @@ void MainWindow::loadConfig(const QString &fname) } } - QStringList excludefx{settings.value("excludefx").toStringList()}; + 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("eaxreverb", Qt::CaseInsensitive)); - ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive)); - ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive)); - ui->enableChorusCheck->setChecked(!excludefx.contains("chorus", Qt::CaseInsensitive)); - ui->enableCompressorCheck->setChecked(!excludefx.contains("compressor", Qt::CaseInsensitive)); - ui->enableDistortionCheck->setChecked(!excludefx.contains("distortion", Qt::CaseInsensitive)); - ui->enableEchoCheck->setChecked(!excludefx.contains("echo", Qt::CaseInsensitive)); - ui->enableEqualizerCheck->setChecked(!excludefx.contains("equalizer", Qt::CaseInsensitive)); - ui->enableFlangerCheck->setChecked(!excludefx.contains("flanger", Qt::CaseInsensitive)); - ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains("fshifter", Qt::CaseInsensitive)); - ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive)); - ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive)); - ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive)); - ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive)); + 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("eax/enable")) != Qt::Unchecked); + ui->enableEaxCheck->setChecked(getCheckState(settings.value(QStringLiteral("eax/enable"))) != Qt::Unchecked); - ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server"))); - ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves"))); - ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate"))); - ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency"))); + 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("pipewire/assume-audio"))); - ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix"))); + 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("wasapi/allow-resampler"))); + ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("wasapi/allow-resampler")))); - ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); - ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); - ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix"))); - ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString()); + 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("alsa/device", QString()).toString()); - ui->alsaDefaultCaptureLine->setText(settings.value("alsa/capture", QString()).toString()); - ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value("alsa/allow-resampler"))); - ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap"))); + 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("oss/device", QString()).toString()); - ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString()); + 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("solaris/device", QString()).toString()); + ui->solarisDefaultDeviceLine->setText(settings.value(QStringLiteral("solaris/device"), QString()).toString()); - ui->waveOutputLine->setText(settings.value("wave/file", QString()).toString()); - ui->waveBFormatCheckBox->setChecked(settings.value("wave/bformat", false).toBool()); + 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")); @@ -969,72 +992,72 @@ void MainWindow::saveConfig(const QString &fname) const /* HACK: Compound any stringlist values into a comma-separated string. */ QStringList allkeys{settings.allKeys()}; - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QStringList vals{settings.value(key).toStringList()}; if(vals.size() > 1) settings.setValue(key, vals.join(QChar(','))); } - settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); - settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); + settings.setValue(QStringLiteral("sample-type"), getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); + settings.setValue(QStringLiteral("channels"), getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); uint rate{ui->sampleRateCombo->currentText().toUInt()}; if(rate <= 0) - settings.setValue("frequency", QString{}); + settings.setValue(QStringLiteral("frequency"), QString{}); else - settings.setValue("frequency", rate); + settings.setValue(QStringLiteral("frequency"), rate); - settings.setValue("period_size", ui->periodSizeEdit->text()); - settings.setValue("periods", ui->periodCountEdit->text()); + settings.setValue(QStringLiteral("period_size"), ui->periodSizeEdit->text()); + settings.setValue(QStringLiteral("periods"), ui->periodCountEdit->text()); - settings.setValue("sources", ui->srcCountLineEdit->text()); - settings.setValue("slots", ui->effectSlotLineEdit->text()); + settings.setValue(QStringLiteral("sources"), ui->srcCountLineEdit->text()); + settings.setValue(QStringLiteral("slots"), ui->effectSlotLineEdit->text()); - settings.setValue("resampler", std::data(resamplerList[ui->resamplerSlider->value()].value)); + settings.setValue(QStringLiteral("resampler"), std::data(resamplerList[ui->resamplerSlider->value()].value)); - settings.setValue("stereo-mode", getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); - settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); - settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); + 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("output-limiter", getCheckValue(ui->outputLimiterCheckBox)); - settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox)); + settings.setValue(QStringLiteral("output-limiter"), getCheckValue(ui->outputLimiterCheckBox)); + settings.setValue(QStringLiteral("dither"), getCheckValue(ui->outputDitherCheckBox)); - settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); - settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); - settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); + settings.setValue(QStringLiteral("decoder/hq-mode"), getCheckValue(ui->decoderHQModeCheckBox)); + settings.setValue(QStringLiteral("decoder/distance-comp"), getCheckValue(ui->decoderDistCompCheckBox)); + settings.setValue(QStringLiteral("decoder/nfc"), getCheckValue(ui->decoderNFEffectsCheckBox)); double speakerdist{ui->decoderSpeakerDistSpinBox->value()}; - settings.setValue("decoder/speaker-dist", + settings.setValue(QStringLiteral("decoder/speaker-dist"), (speakerdist != 1.0) ? QString::number(speakerdist) : QString{} ); - settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text()); - settings.setValue("decoder/surround51", ui->decoder51LineEdit->text()); - settings.setValue("decoder/surround61", ui->decoder61LineEdit->text()); - settings.setValue("decoder/surround71", ui->decoder71LineEdit->text()); - settings.setValue("decoder/surround3d71", ui->decoder3D71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/quad"), ui->decoderQuadLineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround51"), ui->decoder51LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround61"), ui->decoder61LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround71"), ui->decoder71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround3d71"), ui->decoder3D71LineEdit->text()); QStringList strlist; if(!ui->enableSSECheckBox->isChecked()) - strlist.append("sse"); + strlist.append(QStringLiteral("sse")); if(!ui->enableSSE2CheckBox->isChecked()) - strlist.append("sse2"); + strlist.append(QStringLiteral("sse2")); if(!ui->enableSSE3CheckBox->isChecked()) - strlist.append("sse3"); + strlist.append(QStringLiteral("sse3")); if(!ui->enableSSE41CheckBox->isChecked()) - strlist.append("sse4.1"); + strlist.append(QStringLiteral("sse4.1")); if(!ui->enableNeonCheckBox->isChecked()) - strlist.append("neon"); - settings.setValue("disable-cpu-exts", strlist.join(QChar(','))); + strlist.append(QStringLiteral("neon")); + settings.setValue(QStringLiteral("disable-cpu-exts"), strlist.join(QChar(','))); - settings.setValue("hrtf-mode", std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); + settings.setValue(QStringLiteral("hrtf-mode"), std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); if(ui->preferredHrtfComboBox->currentIndex() == 0) - settings.setValue("default-hrtf", QString{}); + settings.setValue(QStringLiteral("default-hrtf"), QString{}); else { QString str{ui->preferredHrtfComboBox->currentText()}; - settings.setValue("default-hrtf", str); + settings.setValue(QStringLiteral("default-hrtf"), str); } strlist.clear(); @@ -1043,7 +1066,7 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append(ui->hrtfFileList->item(i)->text()); if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("hrtf-paths", strlist.join(QChar{','})); + settings.setValue(QStringLiteral("hrtf-paths"), strlist.join(QChar{','})); strlist.clear(); for(int i = 0;i < ui->enabledBackendList->count();i++) @@ -1071,92 +1094,92 @@ void MainWindow::saveConfig(const QString &fname) const } } if(strlist.empty() && !ui->backendCheckBox->isChecked()) - strlist.append("-all"); + strlist.append(QStringLiteral("-all")); else if(ui->backendCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("drivers", strlist.join(QChar(','))); + settings.setValue(QStringLiteral("drivers"), strlist.join(QChar(','))); // TODO: Remove check when we can properly match global values. if(ui->defaultReverbComboBox->currentIndex() == 0) - settings.setValue("default-reverb", QString{}); + settings.setValue(QStringLiteral("default-reverb"), QString{}); else { QString str{ui->defaultReverbComboBox->currentText().toLower()}; - settings.setValue("default-reverb", str); + settings.setValue(QStringLiteral("default-reverb"), str); } strlist.clear(); if(!ui->enableEaxReverbCheck->isChecked()) - strlist.append("eaxreverb"); + strlist.append(QStringLiteral("eaxreverb")); if(!ui->enableStdReverbCheck->isChecked()) - strlist.append("reverb"); + strlist.append(QStringLiteral("reverb")); if(!ui->enableAutowahCheck->isChecked()) - strlist.append("autowah"); + strlist.append(QStringLiteral("autowah")); if(!ui->enableChorusCheck->isChecked()) - strlist.append("chorus"); + strlist.append(QStringLiteral("chorus")); if(!ui->enableDistortionCheck->isChecked()) - strlist.append("distortion"); + strlist.append(QStringLiteral("distortion")); if(!ui->enableCompressorCheck->isChecked()) - strlist.append("compressor"); + strlist.append(QStringLiteral("compressor")); if(!ui->enableEchoCheck->isChecked()) - strlist.append("echo"); + strlist.append(QStringLiteral("echo")); if(!ui->enableEqualizerCheck->isChecked()) - strlist.append("equalizer"); + strlist.append(QStringLiteral("equalizer")); if(!ui->enableFlangerCheck->isChecked()) - strlist.append("flanger"); + strlist.append(QStringLiteral("flanger")); if(!ui->enableFrequencyShifterCheck->isChecked()) - strlist.append("fshifter"); + strlist.append(QStringLiteral("fshifter")); if(!ui->enableModulatorCheck->isChecked()) - strlist.append("modulator"); + strlist.append(QStringLiteral("modulator")); if(!ui->enableDedicatedCheck->isChecked()) - strlist.append("dedicated"); + strlist.append(QStringLiteral("dedicated")); if(!ui->enablePitchShifterCheck->isChecked()) - strlist.append("pshifter"); + strlist.append(QStringLiteral("pshifter")); if(!ui->enableVocalMorpherCheck->isChecked()) - strlist.append("vmorpher"); - settings.setValue("excludefx", strlist.join(QChar{','})); - settings.setValue("eax/enable", + strlist.append(QStringLiteral("vmorpher")); + settings.setValue(QStringLiteral("excludefx"), strlist.join(QChar{','})); + settings.setValue(QStringLiteral("eax/enable"), (!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked()) - ? QString{/*"true"*/} : QString{"false"}); + ? QString{/*"true"*/} : QStringLiteral("false")); - settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox)); - settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox)); + settings.setValue(QStringLiteral("pipewire/assume-audio"), getCheckValue(ui->pwireAssumeAudioCheckBox)); + settings.setValue(QStringLiteral("pipewire/rt-mix"), getCheckValue(ui->pwireRtMixCheckBox)); - settings.setValue("wasapi/allow-resampler", getCheckValue(ui->wasapiResamplerCheckBox)); + settings.setValue(QStringLiteral("wasapi/allow-resampler"), getCheckValue(ui->wasapiResamplerCheckBox)); - settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox)); - settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox)); - settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox)); - settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox)); + 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("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); - settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); - settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); - settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text()); + 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("alsa/device", ui->alsaDefaultDeviceLine->text()); - settings.setValue("alsa/capture", ui->alsaDefaultCaptureLine->text()); - settings.setValue("alsa/allow-resampler", getCheckValue(ui->alsaResamplerCheckBox)); - settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox)); + 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("oss/device", ui->ossDefaultDeviceLine->text()); - settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text()); + settings.setValue(QStringLiteral("oss/device"), ui->ossDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("oss/capture"), ui->ossDefaultCaptureLine->text()); - settings.setValue("solaris/device", ui->solarisDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("solaris/device"), ui->solarisDefaultDeviceLine->text()); - settings.setValue("wave/file", ui->waveOutputLine->text()); - settings.setValue("wave/bformat", - ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/} + 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(); - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QString str{settings.value(key).toString()}; - if(str == QString{}) + if(str.isEmpty()) settings.remove(key); } } @@ -1190,11 +1213,7 @@ void MainWindow::updatePeriodSizeSlider() { int pos = ui->periodSizeEdit->text().toInt(); if(pos >= 64) - { - if(pos > 8192) - pos = 8192; - ui->periodSizeSlider->setSliderPosition(pos); - } + ui->periodSizeSlider->setSliderPosition(std::min(pos, 8192)); enableApplyButton(); } @@ -1281,7 +1300,7 @@ void MainWindow::updateHrtfModeLabel(int num) void MainWindow::addHrtfFile() { QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))}; - if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path)) + if(path.isEmpty() == false && !getAllDataPaths(QStringLiteral("/openal/hrtf")).contains(path)) { ui->hrtfFileList->addItem(path); enableApplyButton(); diff --git a/libs/openal-soft/utils/alsoft-config/mainwindow.h b/libs/openal-soft/utils/alsoft-config/mainwindow.h index 2d4cc3fd..9596793e 100644 --- a/libs/openal-soft/utils/alsoft-config/mainwindow.h +++ b/libs/openal-soft/utils/alsoft-config/mainwindow.h @@ -13,7 +13,7 @@ class MainWindow; class MainWindow : public QMainWindow { Q_OBJECT -private slots: +private Q_SLOTS: void cancelCloseAction(); void saveCurrentConfig(); @@ -29,7 +29,7 @@ private slots: void updatePeriodSizeEdit(int size); void updatePeriodSizeSlider(); - void updatePeriodCountEdit(int size); + void updatePeriodCountEdit(int count); void updatePeriodCountSlider(); void selectQuadDecoderFile(); @@ -76,7 +76,7 @@ private: void closeEvent(QCloseEvent *event) override; - void selectDecoderFile(QLineEdit *line, const char *name); + void selectDecoderFile(QLineEdit *line, const char *caption); QStringList collectHrtfs(); diff --git a/libs/openal-soft/utils/getopt.c b/libs/openal-soft/utils/getopt.c deleted file mode 100644 index ab1a246e..00000000 --- a/libs/openal-soft/utils/getopt.c +++ /dev/null @@ -1,137 +0,0 @@ -/* $NetBSD: getopt.c,v 1.26 2003/08/07 16:43:40 agc Exp $ */ - -/* - * Copyright (c) 1987, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. 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. - * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; -#endif /* LIBC_SCCS and not lint */ -#include -#include -#include -#include "getopt.h" - -int opterr = 1, /* if error message should be printed */ - optind = 1, /* index into parent argv vector */ - optopt, /* character checked for validity */ - optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * Get program name in Windows - */ -const char * _getprogname(void); - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt(int nargc, char * const nargv[], const char *ostr) -{ - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - - if (optreset || *place == 0) { /* update scanning pointer */ - optreset = 0; - place = nargv[optind]; - if (optind >= nargc || *place++ != '-') { - /* Argument is absent or is not an option */ - place = EMSG; - return (-1); - } - optopt = *place++; - if (optopt == '-' && *place == 0) { - /* "--" => end of options */ - ++optind; - place = EMSG; - return (-1); - } - if (optopt == 0) { - /* Solitary '-', treat as a '-' option - if the program (eg su) is looking for it. */ - place = EMSG; - if (strchr(ostr, '-') == NULL) - return (-1); - optopt = '-'; - } - } else - optopt = *place++; - - /* See if option letter is one the caller wanted... */ - if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { - if (*place == 0) - ++optind; - if (opterr && *ostr != ':') - (void)fprintf(stderr, - "%s: illegal option -- %c\n", _getprogname(), - optopt); - return (BADCH); - } - - /* Does this option need an argument? */ - if (oli[1] != ':') { - /* don't need argument */ - optarg = NULL; - if (*place == 0) - ++optind; - } else { - /* Option-argument is either the rest of this argument or the - entire next argument. */ - if (*place) - optarg = place; - else if (nargc > ++optind) - optarg = nargv[optind]; - else { - /* option-argument absent */ - place = EMSG; - if (*ostr == ':') - return (BADARG); - if (opterr) - (void)fprintf(stderr, - "%s: option requires an argument -- %c\n", - _getprogname(), optopt); - return (BADCH); - } - place = EMSG; - ++optind; - } - return (optopt); /* return option letter */ -} - -const char * _getprogname() { - char *pgmptr = NULL; - _get_pgmptr(&pgmptr); - return strrchr(pgmptr,'\\')+1; -} - diff --git a/libs/openal-soft/utils/getopt.h b/libs/openal-soft/utils/getopt.h deleted file mode 100644 index f894d9d9..00000000 --- a/libs/openal-soft/utils/getopt.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GETOPT_H -#define GETOPT_H - -#ifndef _WIN32 - -#include - -#else /* _WIN32 */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -extern char *optarg; -extern int optind, opterr, optopt, optreset; - -int getopt(int nargc, char * const nargv[], const char *ostr); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* !_WIN32 */ - -#endif /* !GETOPT_H */ - diff --git a/libs/openal-soft/utils/makemhr/loaddef.cpp b/libs/openal-soft/utils/makemhr/loaddef.cpp index bb274183..e62f5aab 100644 --- a/libs/openal-soft/utils/makemhr/loaddef.cpp +++ b/libs/openal-soft/utils/makemhr/loaddef.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -45,11 +44,18 @@ #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" +#include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" +#include "sofa-support.h" #include "mysofa.h" +namespace { + +using namespace std::string_view_literals; + // Constants for accessing the token reader's ring buffer. constexpr uint TRRingBits{16}; constexpr uint TRRingSize{1 << TRRingBits}; @@ -61,22 +67,18 @@ constexpr uint TRLoadSize{TRRingSize >> 2}; // Token reader state for parsing the data set definition. struct TokenReaderT { std::istream &mIStream; - const char *mName{}; + std::string mName; uint mLine{}; uint mColumn{}; std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; - TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } + explicit TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } TokenReaderT(const TokenReaderT&) = default; }; -// The maximum identifier length used when processing the data set -// definition. -constexpr uint MaxIdentLen{16}; - // The limits for the listener's head 'radius' in the data set definition. constexpr double MinRadius{0.05}; constexpr double MaxRadius{0.15}; @@ -154,7 +156,7 @@ struct SourceRefT { double mRadius; uint mSkip; uint mOffset; - std::array mPath; + std::string mPath; }; @@ -165,55 +167,50 @@ struct SourceRefT { // Setup the reader on the given file. The filename can be NULL if no error // output is desired. -static void TrSetup(const char *startbytes, std::streamsize startbytecount, const char *filename, +void TrSetup(const al::span startbytes, const std::string_view filename, TokenReaderT *tr) { - const char *name = nullptr; + std::string_view namepart; - if(filename) + if(!filename.empty()) { - const char *slash = strrchr(filename, '/'); - if(slash) - { - const char *bslash = strrchr(slash+1, '\\'); - if(bslash) name = bslash+1; - else name = slash+1; - } - else - { - const char *bslash = strrchr(filename, '\\'); - if(bslash) name = bslash+1; - else name = filename; - } + const auto fslashpos = filename.rfind('/'); + const auto bslashpos = filename.rfind('\\'); + const auto slashpos = (bslashpos >= filename.size()) ? fslashpos : + (fslashpos >= filename.size()) ? bslashpos : + std::max(fslashpos, bslashpos); + if(slashpos < filename.size()) + namepart = filename.substr(slashpos+1); } - tr->mName = name; + tr->mName = namepart; tr->mLine = 1; tr->mColumn = 1; tr->mIn = 0; tr->mOut = 0; - if(startbytecount > 0) + if(!startbytes.empty()) { - std::copy_n(startbytes, startbytecount, std::begin(tr->mRing)); - tr->mIn += startbytecount; + assert(startbytes.size() <= tr->mRing.size()); + std::copy(startbytes.cbegin(), startbytes.cend(), tr->mRing.begin()); + tr->mIn += std::streamsize(startbytes.size()); } } // Prime the reader's ring buffer, and return a result indicating that there // is text to process. -static int TrLoad(TokenReaderT *tr) +auto TrLoad(TokenReaderT *tr) -> int { std::istream &istream = tr->mIStream; - std::streamsize toLoad{TRRingSize - static_cast(tr->mIn - tr->mOut)}; + auto toLoad = std::streamsize{TRRingSize} - (tr->mIn - tr->mOut); if(toLoad >= TRLoadSize && istream.good()) { // Load TRLoadSize (or less if at the end of the file) per read. toLoad = TRLoadSize; - const auto in = tr->mIn&TRRingMask; - std::streamsize count{TRRingSize - in}; + const auto in = tr->mIn & std::streamsize{TRRingMask}; + const auto count = std::streamsize{TRRingSize} - in; if(count < toLoad) { istream.read(al::to_address(tr->mRing.begin() + in), count); @@ -239,38 +236,26 @@ static int TrLoad(TokenReaderT *tr) } // Error display routine. Only displays when the base name is not NULL. -static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr) -{ - if(!tr->mName) - return; - fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName, line, column); - vfprintf(stderr, format, argPtr); -} - // Used to display an error at a saved line/column. -static void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...) +template +void TrErrorAt(const TokenReaderT *tr, uint line, uint column, fmt::format_string fmt, + Args&& ...args) { - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list argPtr; - va_start(argPtr, format); - TrErrorVA(tr, line, column, format, argPtr); - va_end(argPtr); - /* NOLINTEND(*-array-to-pointer-decay) */ + if(tr->mName.empty()) + return; + fmt::print(stderr, "\nError ({}:{}:{}): ", tr->mName, line, column); + fmt::println(stderr, fmt, std::forward(args)...); } // Used to display an error at the current line/column. -static void TrError(const TokenReaderT *tr, const char *format, ...) +template +void TrError(const TokenReaderT *tr, fmt::format_string fmt, Args&& ...args) { - /* NOLINTBEGIN(*-array-to-pointer-decay) */ - va_list argPtr; - va_start(argPtr, format); - TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr); - va_end(argPtr); - /* NOLINTEND(*-array-to-pointer-decay) */ + TrErrorAt(tr, tr->mLine, tr->mColumn, fmt, std::forward(args)...); } // Skips to the next line. -static void TrSkipLine(TokenReaderT *tr) +void TrSkipLine(TokenReaderT *tr) { char ch; @@ -289,7 +274,7 @@ static void TrSkipLine(TokenReaderT *tr) } // Skips to the next token. -static int TrSkipWhitespace(TokenReaderT *tr) +auto TrSkipWhitespace(TokenReaderT *tr) -> int { while(TrLoad(tr)) { @@ -314,7 +299,7 @@ static int TrSkipWhitespace(TokenReaderT *tr) } // Get the line and/or column of the next token (or the end of input). -static void TrIndication(TokenReaderT *tr, uint *line, uint *column) +void TrIndication(TokenReaderT *tr, uint *line, uint *column) { TrSkipWhitespace(tr); if(line) *line = tr->mLine; @@ -323,7 +308,7 @@ static void TrIndication(TokenReaderT *tr, uint *line, uint *column) // 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. -static int TrIsIdent(TokenReaderT *tr) +auto TrIsIdent(TokenReaderT *tr) -> int { if(!TrSkipWhitespace(tr)) return 0; @@ -334,24 +319,20 @@ static int TrIsIdent(TokenReaderT *tr) // Checks to see if a token is the given operator. It does not display any // errors and will not proceed to the next token. -static int TrIsOperator(TokenReaderT *tr, const char *op) +auto TrIsOperator(TokenReaderT *tr, const std::string_view op) -> int { - std::streamsize out; - size_t len; - char ch; - if(!TrSkipWhitespace(tr)) return 0; - out = tr->mOut; - len = 0; - while(op[len] != '\0' && out < tr->mIn) + auto out = tr->mOut; + size_t len{0}; + while(len < op.size() && out < tr->mIn) { - ch = tr->mRing[out&TRRingMask]; - if(ch != op[len]) break; - len++; - out++; + if(tr->mRing[out&TRRingMask] != op[len]) + break; + ++len; + ++out; } - if(op[len] == '\0') + if(len == op.size()) return 1; return 0; } @@ -362,45 +343,35 @@ static int TrIsOperator(TokenReaderT *tr, const char *op) */ // Reads and validates an identifier token. -static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) +auto TrReadIdent(TokenReaderT *tr) -> std::string { - uint col, len; - char ch; - - col = tr->mColumn; + auto ret = std::string{}; + auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TRRingMask]; + auto ch = char{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '_' || isalpha(ch)) { - len = 0; do { - if(len < maxLen) - ident[len] = ch; - len++; - tr->mOut++; + ret += ch; + tr->mColumn += 1; + tr->mOut += 1; if(!TrLoad(tr)) break; ch = tr->mRing[tr->mOut&TRRingMask]; - } while(ch == '_' || isdigit(ch) || isalpha(ch)); + } while(ch == '_' || std::isdigit(ch) || std::isalpha(ch)); - tr->mColumn += len; - if(len < maxLen) - { - ident[len] = '\0'; - return 1; - } - TrErrorAt(tr, tr->mLine, col, "Identifier is too long.\n"); - return 0; + return ret; } } - TrErrorAt(tr, tr->mLine, col, "Expected an identifier.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Expected an identifier."); + ret.clear(); + return ret; } // Reads and validates (including bounds) an integer token. -static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) +auto TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) -> int { uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) @@ -438,18 +409,18 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value = static_cast(strtol(temp.data(), nullptr, 10)); if(*value < loBound || *value > hiBound) { - TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound); + TrErrorAt(tr, tr->mLine, col, "Expected a value from {} to {}.", loBound, hiBound); return 0; } return 1; } } - TrErrorAt(tr, tr->mLine, col, "Expected an integer.\n"); + TrErrorAt(tr, tr->mLine, col, "Expected an integer."); return 0; } // Reads and validates (including bounds) a float token. -static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) +auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) -> int { uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) @@ -532,7 +503,8 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo *value = strtod(temp.data(), nullptr); if(*value < loBound || *value > hiBound) { - TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound); + TrErrorAt(tr, tr->mLine, col, "Expected a value from {:f} to {:f}.", loBound, + hiBound); return 0; } return 1; @@ -541,25 +513,23 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo else tr->mColumn += len; } - TrErrorAt(tr, tr->mLine, col, "Expected a float.\n"); + TrErrorAt(tr, tr->mLine, col, "Expected a float."); return 0; } // Reads and validates a string token. -static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) +auto TrReadString(TokenReaderT *tr) -> std::optional { - uint col, len; - char ch; + auto ret = std::string{}; - col = tr->mColumn; + auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TRRingMask]; - if(ch == '\"') + if(char ch{tr->mRing[tr->mOut&TRRingMask]}; ch == '\"') { tr->mOut++; - len = 0; + size_t len{0}; while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; @@ -568,56 +538,47 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) break; if(ch == '\n') { - TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line."); + return std::nullopt; } - if(len < maxLen) - text[len] = ch; + ret += ch; len++; } if(ch != '\"') { - tr->mColumn += 1 + len; - TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n"); - return 0; + tr->mColumn += static_cast(1 + len); + TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input."); + return std::nullopt; } - tr->mColumn += 2 + len; - if(len > maxLen) - { - TrErrorAt(tr, tr->mLine, col, "String is too long.\n"); - return 0; - } - text[len] = '\0'; - return 1; + tr->mColumn += static_cast(2 + len); + + return std::optional{std::move(ret)}; } } - TrErrorAt(tr, tr->mLine, col, "Expected a string.\n"); - return 0; + TrErrorAt(tr, tr->mLine, col, "Expected a string."); + return std::nullopt; } // Reads and validates the given operator. -static int TrReadOperator(TokenReaderT *tr, const char *op) +auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int { - uint col, len; - char ch; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - while(op[len] != '\0' && TrLoad(tr)) + size_t len{0}; + while(len < op.size() && TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TRRingMask]; - if(ch != op[len]) break; - len++; - tr->mOut++; + if(tr->mRing[tr->mOut&TRRingMask] != op[len]) + break; + ++len; + tr->mOut += 1; } - tr->mColumn += len; - if(op[len] == '\0') + tr->mColumn += static_cast(len); + if(len == op.size()) return 1; } - TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op); + TrErrorAt(tr, tr->mLine, col, "Expected '{}' operator.", op); return 0; } @@ -628,13 +589,14 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) // Read a binary value of the specified byte order and byte size from a file, // storing it as a 32-bit unsigned integer. -static int ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out) +auto ReadBin4(std::istream &istream, const std::string_view filename, const ByteOrderT order, + const uint bytes, uint32_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), static_cast(bytes)); if(istream.gcount() != bytes) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } uint32_t accum{0}; @@ -657,13 +619,14 @@ static int ReadBin4(std::istream &istream, const char *filename, const ByteOrder // Read a binary value of the specified byte order from a file, storing it as // a 64-bit unsigned integer. -static int ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) +auto ReadBin8(std::istream &istream, const std::string_view filename, const ByteOrderT order, + uint64_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), 8); if(istream.gcount() != 8) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } @@ -691,8 +654,9 @@ static int ReadBin8(std::istream &istream, const char *filename, const ByteOrder * whether they are padded toward the MSB (negative) or LSB (positive). * Floating-point types are not normalized. */ -static int ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, - const ElementTypeT type, const uint bytes, const int bits, double *out) +auto ReadBinAsDouble(std::istream &istream, const std::string_view filename, + const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out) + -> int { *out = 0.0; if(bytes > 4) @@ -730,7 +694,8 @@ static int ReadBinAsDouble(std::istream &istream, const char *filename, const By * result. The sign of the bits should always be positive. This also skips * up to one separator character before the element itself. */ -static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, const uint bits, double *out) +auto ReadAsciiAsDouble(TokenReaderT *tr, const std::string_view filename, const ElementTypeT type, + const uint bits, double *out) -> int { if(TrIsOperator(tr, ",")) TrReadOperator(tr, ","); @@ -746,7 +711,7 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme if(!TrReadFloat(tr, -std::numeric_limits::infinity(), std::numeric_limits::infinity(), out)) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } } @@ -755,7 +720,7 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme int v; if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v)) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } *out = v / static_cast((1<<(bits-1))-1); @@ -765,8 +730,8 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme // Read the RIFF/RIFX WAVE format chunk from a file, validating it against // the source parameters and data set metrics. -static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, - SourceRefT *src) +auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, + SourceRefT *src) -> int { uint32_t fourCC, chunkSize; uint32_t format, channels, rate, dummy, block, size, bits; @@ -775,36 +740,34 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; } while(fourCC != FOURCC_FMT); - if(!ReadBin4(istream, src->mPath.data(), order, 2, &format) - || !ReadBin4(istream, src->mPath.data(), order, 2, &channels) - || !ReadBin4(istream, src->mPath.data(), order, 4, &rate) - || !ReadBin4(istream, src->mPath.data(), order, 4, &dummy) - || !ReadBin4(istream, src->mPath.data(), order, 2, &block)) + if(!ReadBin4(istream, src->mPath, order, 2, &format) + || !ReadBin4(istream, src->mPath, order, 2, &channels) + || !ReadBin4(istream, src->mPath, order, 4, &rate) + || !ReadBin4(istream, src->mPath, order, 4, &dummy) + || !ReadBin4(istream, src->mPath, order, 2, &block)) return 0; block /= channels; if(chunkSize > 14) { - if(!ReadBin4(istream, src->mPath.data(), order, 2, &size)) + if(!ReadBin4(istream, src->mPath, order, 2, &size)) return 0; - size /= 8; - if(block > size) - size = block; + 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.data(), order, 2, &bits)) + if(!ReadBin4(istream, src->mPath, order, 2, &bits)) return 0; if(bits == 0) bits = 8 * size; istream.seekg(4, std::ios::cur); - if(!ReadBin4(istream, src->mPath.data(), order, 2, &format)) + if(!ReadBin4(istream, src->mPath, order, 2, &format)) return 0; istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } @@ -818,32 +781,32 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { - fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported WAVE format in file '{}'.", src->mPath); return 0; } if(src->mChannel >= channels) { - fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Missing source channel in WAVE file '{}'.", src->mPath); return 0; } if(rate != hrirRate) { - fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Mismatched source sample rate in WAVE file '{}'.", + src->mPath); return 0; } if(format == WAVE_FORMAT_PCM) { if(size < 2 || size > 4) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", + src->mPath); return 0; } if(bits < 16 || bits > (8*size)) { - fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Bad significant bits in WAVE file '{}'.", + src->mPath); return 0; } src->mType = ET_INT; @@ -852,8 +815,8 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u { if(size != 4 && size != 8) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", - src->mPath.data()); + fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", + src->mPath); return 0; } src->mType = ET_FP; @@ -865,21 +828,18 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } // Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles. -static int ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { - int pre, post, skip; - uint i; - - pre = static_cast(src->mSize * src->mChannel); - post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); - skip = 0; - for(i = 0;i < n;i++) + 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.data(), order, src->mType, src->mSize, src->mBits, + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; skip = post; @@ -891,8 +851,8 @@ static int ReadWaveData(std::istream &istream, const SourceRefT *src, const Byte // Read the RIFF/RIFX WAVE list or data chunk, converting all elements to // doubles. -static int ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { uint32_t fourCC, chunkSize, listSize, count; uint block, skip, offset, i; @@ -900,28 +860,28 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte for(;;) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; if(fourCC == FOURCC_DATA) { block = src->mSize * src->mSkip; count = chunkSize / block; - if(count < (src->mOffset + n)) + if(count < (src->mOffset + hrir.size())) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } using off_type = std::istream::off_type; istream.seekg(off_type(src->mOffset) * off_type(block), std::ios::cur); - if(!ReadWaveData(istream, src, order, n, &hrir[0])) + if(!ReadWaveData(istream, src, order, hrir)) return 0; return 1; } if(fourCC == FOURCC_LIST) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) @@ -935,10 +895,10 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte skip = src->mOffset; offset = 0; lastSample = 0.0; - while(offset < n && listSize > 8) + while(offset < hrir.size() && listSize > 8) { - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) @@ -951,9 +911,9 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte chunkSize -= skip * block; count -= skip; skip = 0; - if(count > (n - offset)) - count = n - offset; - if(!ReadWaveData(istream, src, order, count, &hrir[offset])) + 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; @@ -967,15 +927,15 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte } else if(fourCC == FOURCC_SLNT) { - if(!ReadBin4(istream, src->mPath.data(), order, 4, &count)) + if(!ReadBin4(istream, src->mPath, order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) { count -= skip; skip = 0; - if(count > (n - offset)) - count = n - offset; + if(count > (hrir.size() - offset)) + count = static_cast(hrir.size() - offset); for(i = 0; i < count; i ++) hrir[offset + i] = lastSample; offset += count; @@ -989,9 +949,9 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } - if(offset < n) + if(offset < hrir.size()) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } return 1; @@ -999,29 +959,27 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte // Load a source HRIR from an ASCII text file containing a list of elements // separated by whitespace or common list operators (',', ';', ':', '|'). -static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, - const uint n, double *hrir) +auto LoadAsciiSource(std::istream &istream, const SourceRefT *src, const al::span hrir) -> int { TokenReaderT tr{istream}; - TrSetup(nullptr, 0, nullptr, &tr); + TrSetup({}, {}, &tr); for(uint i{0};i < src->mOffset;++i) { double dummy{}; - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), - &dummy)) + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return 0; } - for(uint i{0};i < n;++i) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) return 0; for(uint j{0};j < src->mSkip;++j) { double dummy{}; - if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, - static_cast(src->mBits), &dummy)) + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), + &dummy)) return 0; } } @@ -1029,13 +987,13 @@ static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, } // Load a source HRIR from a binary file. -static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { istream.seekg(static_cast(src->mOffset), std::ios::beg); - for(uint i{0};i < n;i++) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; if(src->mSkip > 0) @@ -1045,14 +1003,14 @@ static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const } // Load a source HRIR from a RIFF/RIFX WAVE file. -static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, - const uint n, double *hrir) +auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, + const al::span hrir) -> int { uint32_t fourCC, dummy; ByteOrderT order; - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &dummy)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy)) return 0; if(fourCC == FOURCC_RIFF) order = BO_LITTLE; @@ -1060,27 +1018,25 @@ static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hri order = BO_BIG; else { - fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: No RIFF/RIFX chunk in file '{}'.", src->mPath); return 0; } - if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; if(fourCC != FOURCC_WAVE) { - fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Not a RIFF/RIFX WAVE file '{}'.", src->mPath); return 0; } if(!ReadWaveFormat(istream, order, hrirRate, src)) return 0; - if(!ReadWaveList(istream, src, order, n, hrir)) + if(!ReadWaveList(istream, src, order, hrir)) return 0; return 1; } -namespace { - struct SofaEasyDeleter { void operator()(gsl::owner sofa) { @@ -1099,12 +1055,10 @@ struct SofaCacheEntry { }; std::vector gSofaCache; -} // namespace - // Load a Spatially Oriented Format for Accoustics (SOFA) file. -static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) +auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_EASY* { - const std::string_view srcname{src->mPath.data()}; + const std::string_view srcname{src->mPath}; auto iter = std::find_if(gSofaCache.begin(), gSofaCache.end(), [srcname,hrirRate](SofaCacheEntry &entry) -> bool { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); @@ -1113,40 +1067,40 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin SofaEasyPtr sofa{new(std::nothrow) MYSOFA_EASY{}}; if(!sofa) { - fprintf(stderr, "\nError: Out of memory.\n"); + fmt::println(stderr, "\nError: Out of memory."); return nullptr; } sofa->lookup = nullptr; sofa->neighborhood = nullptr; int err; - sofa->hrtf = mysofa_load(src->mPath.data(), &err); + sofa->hrtf = mysofa_load(src->mPath.c_str(), &err); if(!sofa->hrtf) { - fprintf(stderr, "\nError: Could not load source file '%s' (error: %d).\n", - src->mPath.data(), err); + fmt::println(stderr, "\nError: Could not load source file '{}': {} ({}).", src->mPath, + SofaErrorStr(err), err); return nullptr; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofa->hrtf); if(err != MYSOFA_OK) - fprintf(stderr, "\nWarning: Supposedly malformed source file '%s' (error: %d).\n", - src->mPath.data(), err); + fmt::println(stderr, "\nWarning: Supposedly malformed source file '{}': {} ({}).", + src->mPath, SofaErrorStr(err), err); if((src->mOffset + n) > sofa->hrtf->N) { - fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Not enough samples in SOFA file '{}'.", src->mPath); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { - fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n",src->mPath.data()); + fmt::println(stderr, "\nError: Missing source receiver in SOFA file '{}'.", src->mPath); return nullptr; } mysofa_tocartesian(sofa->hrtf); sofa->lookup = mysofa_lookup_init(sofa->hrtf); if(sofa->lookup == nullptr) { - fprintf(stderr, "\nError: Out of memory.\n"); + fmt::println(stderr, "\nError: Out of memory."); return nullptr; } gSofaCache.emplace_back(SofaCacheEntry{std::string{srcname}, hrirRate, std::move(sofa)}); @@ -1154,17 +1108,19 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin } // Copies the HRIR data from a particular SOFA measurement. -static void ExtractSofaHrir(const MYSOFA_EASY *sofa, const uint index, const uint channel, const uint offset, const uint n, double *hrir) +void ExtractSofaHrir(const MYSOFA_HRTF *hrtf, const size_t index, const size_t channel, + const size_t offset, const al::span hrir) { - for(uint i{0u};i < n;i++) - hrir[i] = sofa->hrtf->DataIR.values[(index*sofa->hrtf->R + channel)*sofa->hrtf->N + offset + i]; + const auto irValues = al::span{hrtf->DataIR.values, hrtf->DataIR.elements} + .subspan((index*hrtf->R + channel)*hrtf->N + offset); + std::copy_n(irValues.cbegin(), hrir.size(), hrir.begin()); } // Load a source HRIR from a Spatially Oriented Format for Accoustics (SOFA) // file. -static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, n)}; + MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, static_cast(hrir.size()))}; if(sofa == nullptr) return 0; /* NOTE: At some point it may be beneficial or necessary to consider the @@ -1181,96 +1137,86 @@ static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, do int nearest{mysofa_lookup(sofa->lookup, target.data())}; if(nearest < 0) { - fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Lookup failed in source file '{}'.", src->mPath); return 0; } - al::span coords{&sofa->hrtf->SourcePosition.values[3_z * nearest], 3}; + al::span coords = al::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz} + .subspan(static_cast(nearest)*3_uz).first<3>(); if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 || std::abs(coords[2] - target[2]) > 0.001) { - fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", - src->mRadius, src->mElevation, src->mAzimuth, src->mPath.data()); + fmt::println(stderr, + "\nError: No impulse response at coordinates ({:.3f}r, {:.1f}ev, {:.1f}az) in file '{}'.", + src->mRadius, src->mElevation, src->mAzimuth, src->mPath); target[0] = coords[0]; target[1] = coords[1]; target[2] = coords[2]; mysofa_c2s(target.data()); - fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], - target[1], target[0]); + fmt::println(stderr, " Nearest candidate at ({:.3f}r, {:.1f}ev, {:.1f}az).", + target[2], target[1], target[0]); return 0; } - ExtractSofaHrir(sofa, static_cast(nearest), src->mChannel, src->mOffset, n, hrir); + ExtractSofaHrir(sofa->hrtf, static_cast(nearest), src->mChannel, src->mOffset, hrir); return 1; } // Load a source HRIR from a supported file type. -static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - std::unique_ptr istream; + auto istream = fs::ifstream{}; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) - istream = std::make_unique(std::filesystem::u8path(src->mPath.data())); + istream.open(fs::u8path(src->mPath)); else - istream = std::make_unique(std::filesystem::u8path(src->mPath.data()), - std::ios::binary); - if(!istream->good()) + istream.open(fs::u8path(src->mPath), std::ios::binary); + if(!istream.good()) { - fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath.data()); + fmt::println(stderr, "\nError: Could not open source file '{}'.", src->mPath); return 0; } } - int result{0}; + switch(src->mFormat) { - case SF_ASCII: - result = LoadAsciiSource(*istream, src, n, hrir); - break; - case SF_BIN_LE: - result = LoadBinarySource(*istream, src, BO_LITTLE, n, hrir); - break; - case SF_BIN_BE: - result = LoadBinarySource(*istream, src, BO_BIG, n, hrir); - break; - case SF_WAVE: - result = LoadWaveSource(*istream, src, hrirRate, n, hrir); - break; - case SF_SOFA: - result = LoadSofaSource(src, hrirRate, n, hrir); - break; - case SF_NONE: - break; + case SF_ASCII: return LoadAsciiSource(istream, src, hrir); + case SF_BIN_LE: return LoadBinarySource(istream, src, BO_LITTLE, hrir); + case SF_BIN_BE: return LoadBinarySource(istream, src, BO_BIG, hrir); + case SF_WAVE: return LoadWaveSource(istream, src, hrirRate, hrir); + case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); + case SF_NONE: break; } - return result; + return 0; } // Match the channel type from a given identifier. -static ChannelTypeT MatchChannelType(const char *ident) +auto MatchChannelType(const std::string_view ident) -> ChannelTypeT { - if(al::strcasecmp(ident, "mono") == 0) + if(al::case_compare(ident, "mono"sv) == 0) return CT_MONO; - if(al::strcasecmp(ident, "stereo") == 0) + 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. -static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) +auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, + const ChannelModeT chanMode, HrirDataT *hData) -> int { int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0; int hasDistance = 0, hasAzimuths = 0; - std::array ident; uint line, col; double fpVal; uint points; int intVal; - std::array distances; + std::array distances{}; uint fdCount = 0; - std::array evCounts; + std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); @@ -1278,13 +1224,14 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MaxIdentLen, ident.data())) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - if(al::strcasecmp(ident.data(), "rate") == 0) + if(al::case_compare(ident, "rate"sv) == 0) { if(hasRate) { - TrErrorAt(tr, line, col, "Redefinition of 'rate'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'rate'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1294,24 +1241,23 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrRate = static_cast(intVal); hasRate = 1; } - else if(al::strcasecmp(ident.data(), "type") == 0) + else if(al::case_compare(ident, "type"sv) == 0) { - std::array type; - if(hasType) { - TrErrorAt(tr, line, col, "Redefinition of 'type'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'type'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadIdent(tr, MaxIdentLen, type.data())) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - hData->mChannelType = MatchChannelType(type.data()); + hData->mChannelType = MatchChannelType(type); if(hData->mChannelType == CT_NONE) { - TrErrorAt(tr, line, col, "Expected a channel type.\n"); + TrErrorAt(tr, line, col, "Expected a channel type."); return 0; } if(hData->mChannelType == CT_STEREO) @@ -1321,11 +1267,11 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } hasType = 1; } - else if(al::strcasecmp(ident.data(), "points") == 0) + else if(al::case_compare(ident, "points"sv) == 0) { if(hasPoints) { - TrErrorAt(tr, line, col, "Redefinition of 'points'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'points'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1336,26 +1282,24 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc points = static_cast(intVal); if(fftSize > 0 && points > fftSize) { - TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size.\n"); + TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size."); return 0; } if(points < truncSize) { - TrErrorAt(tr, line, col, "Value is below the truncation size.\n"); + TrErrorAt(tr, line, col, "Value is below the truncation size."); return 0; } hData->mIrPoints = points; hData->mFftSize = fftSize; - hData->mIrSize = 1 + (fftSize / 2); - if(points > hData->mIrSize) - hData->mIrSize = points; + hData->mIrSize = std::max(points, 1u + (fftSize/2u)); hasPoints = 1; } - else if(al::strcasecmp(ident.data(), "radius") == 0) + else if(al::case_compare(ident, "radius"sv) == 0) { if(hasRadius) { - TrErrorAt(tr, line, col, "Redefinition of 'radius'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'radius'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1365,13 +1309,13 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mRadius = fpVal; hasRadius = 1; } - else if(al::strcasecmp(ident.data(), "distance") == 0) + else if(al::case_compare(ident, "distance"sv) == 0) { - uint count = 0; + auto count = uint{0}; if(hasDistance) { - TrErrorAt(tr, line, col, "Redefinition of 'distance'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'distance'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1383,7 +1327,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc return 0; if(count > 0 && fpVal <= distances[count - 1]) { - TrError(tr, "Distances are not ascending.\n"); + TrError(tr, "Distances are not ascending."); return 0; } distances[count++] = fpVal; @@ -1391,26 +1335,26 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc break; if(count >= MAX_FD_COUNT) { - TrError(tr, "Exceeded the maximum of %d fields.\n", MAX_FD_COUNT); + TrError(tr, "Exceeded the maximum of {} fields.", MAX_FD_COUNT); return 0; } TrReadOperator(tr, ","); } if(fdCount != 0 && count != fdCount) { - TrError(tr, "Did not match the specified number of %d fields.\n", fdCount); + TrError(tr, "Did not match the specified number of {} fields.", fdCount); return 0; } fdCount = count; hasDistance = 1; } - else if(al::strcasecmp(ident.data(), "azimuths") == 0) + else if(al::case_compare(ident, "azimuths"sv) == 0) { - uint count = 0; + auto count = uint{0}; if(hasAzimuths) { - TrErrorAt(tr, line, col, "Redefinition of 'azimuths'.\n"); + TrErrorAt(tr, line, col, "Redefinition of 'azimuths'."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1426,7 +1370,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc { if(evCounts[count] >= MAX_EV_COUNT) { - TrError(tr, "Exceeded the maximum of %d elevations.\n", MAX_EV_COUNT); + TrError(tr, "Exceeded the maximum of {} elevations.", MAX_EV_COUNT); return 0; } TrReadOperator(tr, ","); @@ -1435,12 +1379,13 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc { if(evCounts[count] < MIN_EV_COUNT) { - TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT); + TrErrorAt(tr, line, col, "Did not reach the minimum of {} azimuth counts.", + MIN_EV_COUNT); return 0; } if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1) { - TrError(tr, "Poles are not singular for field %d.\n", count - 1); + TrError(tr, "Poles are not singular for field {}.", count - 1); return 0; } count++; @@ -1449,7 +1394,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc if(count >= MAX_FD_COUNT) { - TrError(tr, "Exceeded the maximum number of %d fields.\n", MAX_FD_COUNT); + TrError(tr, "Exceeded the maximum number of %d fields.", MAX_FD_COUNT); return 0; } evCounts[count] = 0; @@ -1458,7 +1403,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(fdCount != 0 && count != fdCount) { - TrError(tr, "Did not match the specified number of %d fields.\n", fdCount); + TrError(tr, "Did not match the specified number of %d fields.", fdCount); return 0; } fdCount = count; @@ -1466,19 +1411,19 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } else { - TrErrorAt(tr, line, col, "Expected a metric name.\n"); + TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } TrSkipWhitespace(tr); } if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths)) { - TrErrorAt(tr, line, col, "Expected a metric name.\n"); + TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } if(distances[0] < hData->mRadius) { - TrError(tr, "Distance cannot start below head radius.\n"); + TrError(tr, "Distance cannot start below head radius."); return 0; } if(hData->mChannelType == CT_NONE) @@ -1486,14 +1431,14 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc const auto azs = al::span{azCounts}.first(); if(!PrepareHrirData(al::span{distances}.first(fdCount), evCounts, azs, hData)) { - fprintf(stderr, "Error: Out of memory.\n"); + fmt::println(stderr, "Error: Out of memory."); exit(-1); } return 1; } // Parse an index triplet from the data set definition. -static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai) +auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)->int { int intVal; @@ -1521,46 +1466,46 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, } // Match the source format from a given identifier. -static SourceFormatT MatchSourceFormat(const char *ident) +auto MatchSourceFormat(const std::string_view ident) -> SourceFormatT { - if(al::strcasecmp(ident, "ascii") == 0) + if(al::case_compare(ident, "ascii"sv) == 0) return SF_ASCII; - if(al::strcasecmp(ident, "bin_le") == 0) + if(al::case_compare(ident, "bin_le"sv) == 0) return SF_BIN_LE; - if(al::strcasecmp(ident, "bin_be") == 0) + if(al::case_compare(ident, "bin_be"sv) == 0) return SF_BIN_BE; - if(al::strcasecmp(ident, "wave") == 0) + if(al::case_compare(ident, "wave"sv) == 0) return SF_WAVE; - if(al::strcasecmp(ident, "sofa") == 0) + if(al::case_compare(ident, "sofa"sv) == 0) return SF_SOFA; return SF_NONE; } // Match the source element type from a given identifier. -static ElementTypeT MatchElementType(const char *ident) +auto MatchElementType(const std::string_view ident) -> ElementTypeT { - if(al::strcasecmp(ident, "int") == 0) + if(al::case_compare(ident, "int"sv) == 0) return ET_INT; - if(al::strcasecmp(ident, "fp") == 0) + 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. -static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) +auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int { - std::array ident; uint line, col; double fpVal; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MaxIdentLen, ident.data())) + auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mFormat = MatchSourceFormat(ident.data()); + src->mFormat = MatchSourceFormat(ident); if(src->mFormat == SF_NONE) { - TrErrorAt(tr, line, col, "Expected a source format.\n"); + TrErrorAt(tr, line, col, "Expected a source format."); return 0; } if(!TrReadOperator(tr, "(")) @@ -1603,12 +1548,13 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) else { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MaxIdentLen, ident.data())) + ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mType = MatchElementType(ident.data()); + src->mType = MatchElementType(ident); if(src->mType == ET_NONE) { - TrErrorAt(tr, line, col, "Expected a source element type.\n"); + TrErrorAt(tr, line, col, "Expected a source element type."); return 0; } if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE) @@ -1630,7 +1576,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) return 0; if(std::abs(intVal) < int{MinBinSize}*8 || static_cast(std::abs(intVal)) > (8*src->mSize)) { - TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MinBinSize*8, 8*src->mSize); + TrErrorAt(tr, line, col, "Expected a value of (+/-) {} to {}.", MinBinSize*8, 8*src->mSize); return 0; } src->mBits = intVal; @@ -1643,7 +1589,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) return 0; if(intVal != 4 && intVal != 8) { - TrErrorAt(tr, line, col, "Expected a value of 4 or 8.\n"); + TrErrorAt(tr, line, col, "Expected a value of 4 or 8."); return 0; } src->mSize = static_cast(intVal); @@ -1688,25 +1634,28 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath.data())) + + auto srcpath = TrReadString(tr); + if(!srcpath) return 0; + src->mPath = std::move(*srcpath); return 1; } // Parse and validate a SOFA source reference from the data set definition. -static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) +auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int { - std::array ident; uint line, col; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MaxIdentLen, ident.data())) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - src->mFormat = MatchSourceFormat(ident.data()); + src->mFormat = MatchSourceFormat(ident); if(src->mFormat != SF_SOFA) { - TrErrorAt(tr, line, col, "Expected the SOFA source format.\n"); + TrErrorAt(tr, line, col, "Expected the SOFA source format."); return 0; } @@ -1727,30 +1676,33 @@ static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath.data())) + + auto srcpath = TrReadString(tr); + if(!srcpath) return 0; + src->mPath = std::move(*srcpath); return 1; } // Match the target ear (index) from a given identifier. -static int MatchTargetEar(const char *ident) +auto MatchTargetEar(const std::string_view ident) -> std::optional { - if(al::strcasecmp(ident, "left") == 0) - return 0; - if(al::strcasecmp(ident, "right") == 0) - return 1; - return -1; + 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. -static constexpr int OnsetRateMultiple{10}; -static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, - const uint n, const double *hrir, const double f, const double onset) +constexpr int OnsetRateMultiple{10}; +auto AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, + const al::span hrir, const double f, const double onset) -> double { - rs.process({hrir, n}, upsampled); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return Lerp(onset, static_cast(std::distance(upsampled.cbegin(), iter))/(10*rate), f); @@ -1758,28 +1710,27 @@ static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, // Calculate the magnitude response of an HRIR and average it with any // existing responses for its field, elevation, azimuth, and ear. -static void AverageHrirMagnitude(const uint points, const uint n, const double *hrir, const double f, double *mag) +void AverageHrirMagnitude(const uint fftSize, const al::span hrir, const double f, + const al::span mag) { - uint m = 1 + (n / 2), i; - std::vector h(n); - std::vector r(n); + const uint m{1 + (fftSize/2)}; + std::vector h(fftSize); + std::vector r(m); - for(i = 0;i < points;i++) - h[i] = hrir[i]; - for(;i < n;i++) - h[i] = 0.0; - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), r.data()); - for(i = 0;i < m;i++) + auto hiter = std::copy(hrir.cbegin(), hrir.cend(), h.begin()); + std::fill(hiter, h.end(), 0.0); + forward_fft(h); + MagnitudeResponse(h, r); + for(uint i{0};i < m;++i) mag[i] = Lerp(mag[i], r[i], f); } // Process the list of sources in the data set definition. -static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) +auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> int { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); - double *hrirs = hData->mHrirsBase.data(); + const auto hrirs = al::span{hData->mHrirsBase}; auto hrir = std::vector(hData->mIrSize); uint line, col, fi, ei, ai; @@ -1795,7 +1746,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate ? std::min(static_cast(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints) : hData->mIrPoints}; - printf("Loading sources..."); + fmt::print("Loading sources..."); fflush(stdout); int count{0}; while(TrIsOperator(tr, "[")) @@ -1818,16 +1769,14 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate if(hData->mChannelType == CT_STEREO) { - std::array type{}; - - if(!TrReadIdent(tr, MaxIdentLen, type.data())) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - const ChannelTypeT channelType{MatchChannelType(type.data())}; - switch(channelType) + switch(MatchChannelType(type)) { case CT_NONE: - TrErrorAt(tr, line, col, "Expected a channel type.\n"); + TrErrorAt(tr, line, col, "Expected a channel type."); return 0; case CT_MONO: src.mChannel = 0; @@ -1839,14 +1788,13 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } else { - std::array type{}; - if(!TrReadIdent(tr, MaxIdentLen, type.data())) + const auto type = TrReadIdent(tr); + if(type.empty()) return 0; - ChannelTypeT channelType{MatchChannelType(type.data())}; - if(channelType != CT_MONO) + if(MatchChannelType(type) != CT_MONO) { - TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); + TrErrorAt(tr, line, col, "Expected a mono channel type."); return 0; } src.mChannel = 0; @@ -1855,16 +1803,15 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate MYSOFA_EASY *sofa{LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints)}; if(!sofa) return 0; + const auto srcPosValues = al::span{sofa->hrtf->SourcePosition.values, + sofa->hrtf->M*3_uz}; for(uint si{0};si < sofa->hrtf->M;++si) { - printf("\rLoading sources... %d of %d", si+1, sofa->hrtf->M); + fmt::print("\rLoading sources... {} of {}", si+1, sofa->hrtf->M); fflush(stdout); - std::array aer{ - sofa->hrtf->SourcePosition.values[3_uz*si], - sofa->hrtf->SourcePosition.values[3_uz*si + 1], - sofa->hrtf->SourcePosition.values[3_uz*si + 2] - }; + 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) @@ -1895,31 +1842,32 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate continue; HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); + TrErrorAt(tr, line, col, "Redefinition of source [ {}, {}, {} ].", fi, ei, ai); return 0; } - ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.data()); - azd->mIrs[0] = &hrirs[size_t{hData->mIrSize} * azd->mIndex]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + ExtractSofaHrir(sofa->hrtf, si, 0, src.mOffset, hrirPoints); + azd->mIrs[0] = hrirs.subspan(size_t{hData->mIrSize}*azd->mIndex, hData->mIrSize); azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]); + hrirPoints, 1.0, azd->mDelays[0]); if(resampler) - resampler->process(al::span{hrir}.first(hData->mIrPoints), - al::span{hrir}.first(hData->mIrSize)); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]); + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, + azd->mIrs[0]); if(src.mChannel == 1) { - ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.data()); - azd->mIrs[1] = &hrirs[hData->mIrSize * (size_t{hData->mIrCount}+azd->mIndex)]; + 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, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]); + hData->mIrRate, hrirPoints, 1.0, azd->mDelays[1]); if(resampler) - resampler->process(al::span{hrir}.first(hData->mIrPoints), - al::span{hrir}.first(hData->mIrSize)); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0, + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, azd->mIrs[1]); } @@ -1937,9 +1885,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate return 0; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Redefinition of source.\n"); + TrErrorAt(tr, line, col, "Redefinition of source."); return 0; } if(!TrReadOperator(tr, "=")) @@ -1955,32 +1903,35 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate // require preparing the source refs first to get a total count // before loading them. ++count; - printf("\rLoading sources... %d file%s", count, (count==1)?"":"s"); + fmt::print("\rLoading sources... {} file{}", count, (count==1)?"":"s"); fflush(stdout); - if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.data())) + if(!LoadSource(&src, hData->mIrRate, al::span{hrir}.first(hData->mIrPoints))) return 0; - uint ti{0}; + auto ti = uint{0}; if(hData->mChannelType == CT_STEREO) { - std::array ident{}; - if(!TrReadIdent(tr, MaxIdentLen, ident.data())) + const auto ident = TrReadIdent(tr); + if(ident.empty()) return 0; - ti = static_cast(MatchTargetEar(ident.data())); - if(static_cast(ti) < 0) + + if(auto earopt = MatchTargetEar(ident)) + ti = *earopt; + else { - TrErrorAt(tr, line, col, "Expected a target ear.\n"); + TrErrorAt(tr, line, col, "Expected a target ear."); return 0; } } - azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti*size_t{hData->mIrCount} + azd->mIndex)]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + azd->mIrs[ti] = hrirs.subspan((ti*size_t{hData->mIrCount}+azd->mIndex)*hData->mIrSize, + hData->mIrSize); azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.data(), 1.0 / factor[ti], azd->mDelays[ti]); + hrirPoints, 1.0/factor[ti], azd->mDelays[ti]); if(resampler) - resampler->process(al::span{hrir}.first(hData->mIrPoints), - al::span{hrir}.first(hData->mIrSize)); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0 / factor[ti], + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0/factor[ti], azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) @@ -1989,19 +1940,19 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } if(hData->mChannelType == CT_STEREO) { - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { - TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n"); + TrErrorAt(tr, line, col, "Missing left ear source reference(s)."); return 0; } - if(azd->mIrs[1] == nullptr) + if(azd->mIrs[1].empty()) { - TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n"); + TrErrorAt(tr, line, col, "Missing right ear source reference(s)."); return 0; } } } - printf("\n"); + fmt::println(""); hrir.clear(); if(resampler) { @@ -2016,7 +1967,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate 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] != nullptr) + if(!azd->mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) @@ -2024,7 +1975,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } if(ei >= hData->mFds[fi].mEvs.size()) { - TrError(tr, "Missing source references [ %d, *, * ].\n", fi); + TrError(tr, "Missing source references [ {}, *, * ].", fi); return 0; } hData->mFds[fi].mEvStart = ei; @@ -2034,9 +1985,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { - TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); + TrError(tr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return 0; } } @@ -2051,8 +2002,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate 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[hData->mIrSize * (ti*size_t{hData->mIrCount} + azd->mIndex)]; + azd->mIrs[ti] = hrirs.subspan( + (ti*size_t{hData->mIrCount} + azd->mIndex) * hData->mIrSize, + hData->mIrSize); } } } @@ -2063,19 +2015,20 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate return 1; } - TrError(tr, "Errant data at end of source list.\n"); + TrError(tr, "Errant data at end of source list."); gSofaCache.clear(); return 0; } +} /* namespace */ -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { TokenReaderT tr{istream}; - TrSetup(startbytes, startbytecount, filename, &tr); + TrSetup(startbytes, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) || !ProcessSources(&tr, hData, outRate)) return false; diff --git a/libs/openal-soft/utils/makemhr/loaddef.h b/libs/openal-soft/utils/makemhr/loaddef.h index 63600dcd..3eafc825 100644 --- a/libs/openal-soft/utils/makemhr/loaddef.h +++ b/libs/openal-soft/utils/makemhr/loaddef.h @@ -2,12 +2,15 @@ #define LOADDEF_H #include +#include + +#include "alspan.h" #include "makemhr.h" -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ diff --git a/libs/openal-soft/utils/makemhr/loadsofa.cpp b/libs/openal-soft/utils/makemhr/loadsofa.cpp index cbe8e65d..70d03e63 100644 --- a/libs/openal-soft/utils/makemhr/loadsofa.cpp +++ b/libs/openal-soft/utils/makemhr/loadsofa.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -41,6 +40,7 @@ #include "alspan.h" #include "alnumeric.h" +#include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" @@ -59,14 +59,14 @@ using uint = unsigned int; * possible. Those sets that contain purely random measurements or use * different major axes will fail. */ -bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) +auto PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool { - fprintf(stdout, "Detecting compatible layout...\n"); + fmt::println("Detecting compatible layout..."); - auto fds = GetCompatibleLayout(m, xyzs); + auto fds = GetCompatibleLayout(xyzs); if(fds.size() > MAX_FD_COUNT) { - fprintf(stdout, "Incompatible layout (inumerable radii).\n"); + fmt::println("Incompatible layout (inumerable radii)."); return false; } @@ -91,7 +91,7 @@ bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) ++fi; } - fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m); + fmt::println("Using {} of {} IRs.", ir_total, xyzs.size()/3); const auto azs = al::span{azCounts}.first(); return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } @@ -108,7 +108,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { if(srate_dim) { - fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n"); + fmt::println(stderr, "Duplicate SampleRate.DIMENSION_LIST"); return 0.0f; } srate_dim = srate_attrs->value; @@ -117,53 +117,53 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { if(srate_units) { - fprintf(stderr, "Duplicate SampleRate.Units\n"); + fmt::println(stderr, "Duplicate SampleRate.Units"); return 0.0f; } srate_units = srate_attrs->value; } else - fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name, + fmt::println(stderr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name, srate_attrs->value); srate_attrs = srate_attrs->next; } if(!srate_dim) { - fprintf(stderr, "Missing sample rate dimensions\n"); + fmt::println(stderr, "Missing sample rate dimensions"); return 0.0f; } if(srate_dim != "I"sv) { - fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim); + fmt::println(stderr, "Unsupported sample rate dimensions: {}", srate_dim); return 0.0f; } if(!srate_units) { - fprintf(stderr, "Missing sample rate unit type\n"); + fmt::println(stderr, "Missing sample rate unit type"); return 0.0f; } if(srate_units != "hertz"sv) { - fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units); + fmt::println(stderr, "Unsupported sample rate unit type: {}", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ - if(srate_array->values[0] < float{MIN_RATE} || srate_array->values[0] > float{MAX_RATE}) + const auto values = al::span{srate_array->values, sofaHrtf->I}; + if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { - fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0], + fmt::println(stderr, "Sample rate out of range: {:f} (expected {} to {})", values[0], MIN_RATE, MAX_RATE); return 0.0f; } - return srate_array->values[0]; + return values[0]; } enum class DelayType : uint8_t { None, I_R, /* [1][Channels] */ M_R, /* [HRIRs][Channels] */ - Invalid, }; -DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) +auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional { const char *delay_dim{nullptr}; MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay}; @@ -174,19 +174,19 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) { if(delay_dim) { - fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n"); - return DelayType::Invalid; + fmt::println(stderr, "Duplicate Delay.DIMENSION_LIST"); + return std::nullopt; } delay_dim = delay_attrs->value; } else - fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name, + fmt::println(stderr, "Unexpected delay attribute: {} = {}", delay_attrs->name, delay_attrs->value ? delay_attrs->value : ""); delay_attrs = delay_attrs->next; } if(!delay_dim) { - fprintf(stderr, "Missing delay dimensions\n"); + fmt::println(stderr, "Missing delay dimensions"); return DelayType::None; } if(delay_dim == "I,R"sv) @@ -194,8 +194,8 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) if(delay_dim == "M,R"sv) return DelayType::M_R; - fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); - return DelayType::Invalid; + fmt::println(stderr, "Unsupported delay dimensions: {}", delay_dim); + return std::nullopt; } bool CheckIrData(MYSOFA_HRTF *sofaHrtf) @@ -209,24 +209,24 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) { if(ir_dim) { - fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n"); + fmt::println(stderr, "Duplicate IR.DIMENSION_LIST"); return false; } ir_dim = ir_attrs->value; } else - fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name, + fmt::println(stderr, "Unexpected IR attribute: {} = {}", ir_attrs->name, ir_attrs->value ? ir_attrs->value : ""); ir_attrs = ir_attrs->next; } if(!ir_dim) { - fprintf(stderr, "Missing IR dimensions\n"); + fmt::println(stderr, "Missing IR dimensions"); return false; } if(ir_dim != "M,R,N"sv) { - fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim); + fmt::println(stderr, "Unsupported IR dimensions: {}", ir_dim); return false; } return true; @@ -235,12 +235,12 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) /* Calculate the onset time of a HRIR. */ constexpr int OnsetRateMultiple{10}; -double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, - al::span upsampled, const double *hrir) +auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span upsampled, + const al::span hrir) -> double { - rs.process({hrir, n}, upsampled); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return static_cast(std::distance(upsampled.cbegin(), iter)) / @@ -248,13 +248,13 @@ double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, } /* Calculate the magnitude response of a HRIR. */ -void CalcHrirMagnitude(const uint points, const uint n, al::span h, double *hrir) +void CalcHrirMagnitude(const uint points, al::span h, const al::span hrir) { - auto iter = std::copy_n(hrir, points, h.begin()); + auto iter = std::copy_n(hrir.cbegin(), points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), hrir); + forward_fft(h); + MagnitudeResponse(h, hrir.first((h.size()/2) + 1)); } bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, @@ -266,7 +266,7 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0); - double *hrirs = hData->mHrirsBase.data(); + const auto hrirs = al::span{hData->mHrirsBase}; std::vector restmp; std::optional resampler; @@ -276,15 +276,15 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela restmp.resize(sofaHrtf->N); } + const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}; + const auto irValues = al::span{sofaHrtf->DataIR.values, + size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N}; for(uint si{0u};si < sofaHrtf->M;++si) { loaded_count.fetch_add(1u); - std::array aer{ - sofaHrtf->SourcePosition.values[3_uz*si], - sofaHrtf->SourcePosition.values[3_uz*si + 1], - sofaHrtf->SourcePosition.values[3_uz*si + 2] - }; + 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) @@ -311,40 +311,43 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela 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] != nullptr) + HrirAzT &azd = field->mEvs[ei].mAzs[ai]; + if(!azd.mIrs[0].empty()) { - fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n", + fmt::println(stderr, "\nMultiple measurements near [ a={:f}, e={:f}, r={:f} ].", aer[0], aer[1], aer[2]); return false; } for(uint ti{0u};ti < channels;++ti) { - azd->mIrs[ti] = &hrirs[(size_t{hData->mIrCount}*ti + azd->mIndex)*hData->mIrSize]; + 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(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, azd->mIrs[ti]); + std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin()); else { - std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, restmp.begin()); - resampler->process(restmp, {azd->mIrs[ti], hData->mIrSize}); + std::copy_n(ir.cbegin(), ir.size(), restmp.begin()); + resampler->process(restmp, azd.mIrs[ti]); } } /* Include any per-channel or per-HRIR delays. */ if(delayType == DelayType::I_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->I}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); + azd.mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); } else if(delayType == DelayType::M_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->M}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / + azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / static_cast(hData->mIrRate); } } @@ -363,10 +366,10 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela auto load_future = std::async(std::launch::async, load_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); - printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M); + fmt::print("\rLoading HRIRs... {} of {}", loaded_count.load(), sofaHrtf->M); fflush(stdout); } while(load_status != std::future_status::ready); - fputc('\n', stdout); + fmt::println(""); return load_future.get(); } @@ -378,10 +381,13 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela struct MagCalculator { const uint mFftSize{}; const uint mIrPoints{}; - std::vector mIrs{}; + 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); @@ -401,7 +407,7 @@ struct MagCalculator { */ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); - CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]); + CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]); /* Increment the number of IRs done. */ mDone.fetch_add(1); @@ -411,34 +417,34 @@ struct MagCalculator { } // namespace -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +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(filename, &err)}; + MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)}; if(!sofaHrtf) { - fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err)); + 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) - fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename, - SofaErrorStr(err)); + fmt::println(stderr, "Warning: Supposedly malformed source file '{}': {} ({})", filename, + SofaErrorStr(err), err); mysofa_tocartesian(sofaHrtf.get()); /* Make sure emitter and receiver counts are sane. */ if(sofaHrtf->E != 1) { - fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E); + fmt::println(stderr, "{} emitters not supported", sofaHrtf->E); return false; } if(sofaHrtf->R > 2 || sofaHrtf->R < 1) { - fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R); + fmt::println(stderr, "{} receivers not supported", sofaHrtf->R); return false; } /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */ @@ -450,12 +456,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz /* Check and set the FFT and IR size. */ if(sofaHrtf->N > fftSize) { - fprintf(stderr, "Sample points exceeds the FFT size.\n"); + fmt::println(stderr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N, + fftSize); return false; } if(sofaHrtf->N < truncSize) { - fprintf(stderr, "Sample points is below the truncation size.\n"); + fmt::println(stderr, "Sample points is below the truncation size ({} < {}).", sofaHrtf->N, + truncSize); return false; } hData->mIrPoints = sofaHrtf->N; @@ -469,15 +477,15 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz if(!hData->mIrRate) return false; - DelayType delayType = PrepareDelay(sofaHrtf.get()); - if(delayType == DelayType::Invalid) + const auto delayType = PrepareDelay(sofaHrtf.get()); + if(!delayType) return false; if(!CheckIrData(sofaHrtf.get())) return false; - if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData)) + if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData)) return false; - if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate)) + if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate)) return false; sofaHrtf = nullptr; @@ -490,14 +498,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd.mIrs[0] != nullptr) break; + if(!azd.mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; } if(ei >= hData->mFds[fi].mEvs.size()) { - fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi); + fmt::println(stderr, "Missing source references [ {}, *, * ].", fi); return false; } hData->mFds[fi].mEvStart = ei; @@ -506,9 +514,9 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz 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] == nullptr) + if(azd.mIrs[0].empty()) { - fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); + fmt::println(stderr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return false; } } @@ -518,7 +526,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz size_t hrir_total{0}; const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - double *hrirs = hData->mHrirsBase.data(); + const auto hrirs = al::span{hData->mHrirsBase}; for(uint fi{0u};fi < hData->mFds.size();fi++) { for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) @@ -527,7 +535,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; for(size_t ti{0u};ti < channels;ti++) - azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)]; + azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize, + hData->mIrSize); } } @@ -553,8 +562,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ti{0};ti < channels;ti++) { hrir_done.fetch_add(1u, std::memory_order_acq_rel); - azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints, - upsampled, azd.mIrs[ti]); + azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled, + azd.mIrs[ti].first(hData->mIrPoints)); } } } @@ -566,10 +575,10 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz auto load_future = std::async(std::launch::async, onset_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); - printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total); + fmt::print("\rCalculating HRIR onsets... {} of {}", hrir_done.load(), hrir_total); fflush(stdout); } while(load_status != std::future_status::ready); - fputc('\n', stdout); + fmt::println(""); if(!load_future.get()) return false; @@ -589,16 +598,16 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) - thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator); + thrds.emplace_back(&MagCalculator::Worker, &calculator); size_t count; do { std::this_thread::sleep_for(std::chrono::milliseconds{50}); count = calculator.mDone.load(); - printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size()); + fmt::print("\rCalculating HRIR magnitudes... {} of {}", count, calculator.mIrs.size()); fflush(stdout); } while(count != calculator.mIrs.size()); - fputc('\n', stdout); + fmt::println(""); for(auto &thrd : thrds) { diff --git a/libs/openal-soft/utils/makemhr/loadsofa.h b/libs/openal-soft/utils/makemhr/loadsofa.h index 82dce85a..90d77a0f 100644 --- a/libs/openal-soft/utils/makemhr/loadsofa.h +++ b/libs/openal-soft/utils/makemhr/loadsofa.h @@ -1,10 +1,12 @@ #ifndef LOADSOFA_H #define LOADSOFA_H +#include + #include "makemhr.h" -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +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 */ diff --git a/libs/openal-soft/utils/makemhr/makemhr.cpp b/libs/openal-soft/utils/makemhr/makemhr.cpp index 1722be9b..f51c574c 100644 --- a/libs/openal-soft/utils/makemhr/makemhr.cpp +++ b/libs/openal-soft/utils/makemhr/makemhr.cpp @@ -73,7 +73,6 @@ #include #include #include -#include #include #include #include @@ -82,20 +81,15 @@ #include #include #include -#include #include -#ifdef HAVE_GETOPT -#include -#else -#include "../getopt.h" -#endif - #include "alcomplex.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" +#include "filesystem.h" +#include "fmt/core.h" #include "loaddef.h" #include "loadsofa.h" @@ -169,29 +163,28 @@ enum ChannelIndex : uint { * pattern string are replaced with the replacement string. The result is * truncated if necessary. */ -std::string StrSubst(al::span in, const al::span pat, - const al::span rep) +auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string { std::string ret; ret.reserve(in.size() + pat.size()); while(in.size() >= pat.size()) { - if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0) + if(al::starts_with(in, pat)) { - in = in.subspan(pat.size()); - ret.append(rep.data(), rep.size()); + in = in.substr(pat.size()); + ret += rep; } else { size_t endpos{1}; - while(endpos < in.size() && in[endpos] != pat.front()) + while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front())) ++endpos; - ret.append(in.data(), endpos); - in = in.subspan(endpos); + ret += in.substr(0, endpos); + in = in.substr(endpos); } } - ret.append(in.data(), in.size()); + ret += in; return ret; } @@ -215,73 +208,44 @@ inline uint dither_rng(uint *seed) // Performs a triangular probability density function dither. The input samples // should be normalized (-1 to +1). -void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale, - const uint count, const uint step, uint *seed) +void TpdfDither(const al::span out, const al::span in, const double scale, + const size_t channel, const size_t step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); + assert(channel < step); - for(uint i{0};i < count;i++) + for(size_t i{0};i < in.size();++i) { uint prn0{dither_rng(seed)}; uint prn1{dither_rng(seed)}; - *out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); - out += step; + out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); } } - -/* Calculate the complex helical sequence (or discrete-time analytical signal) - * of the given input using the Hilbert transform. Given the natural logarithm - * of a signal's magnitude response, the imaginary components can be used as - * the angles for minimum-phase reconstruction. - */ -inline void Hilbert(const uint n, complex_d *inout) -{ complex_hilbert({inout, n}); } - -} // namespace - -/* Calculate the magnitude response of the given input. This is used in - * place of phase decomposition, since the phase residuals are discarded for - * minimum phase reconstruction. The mirrored half of the response is also - * discarded. - */ -void MagnitudeResponse(const uint n, const complex_d *in, double *out) -{ - const uint m = 1 + (n / 2); - uint i; - for(i = 0;i < m;i++) - out[i] = std::max(std::abs(in[i]), Epsilon); -} - -namespace { - /* 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 double *in, double *out) +void LimitMagnitudeResponse(const uint n, const uint m, const double limit, + const al::span inout) { - double halfLim; - uint i, lower, upper; - double ave; - - halfLim = limit / 2.0; + const double halfLim{limit / 2.0}; // Convert the response to dB. - for(i = 0;i < m;i++) - out[i] = 20.0 * std::log10(in[i]); + 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. - lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; - upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; - ave = 0.0; - for(i = lower;i <= upper;i++) - ave += out[i]; + 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(i = 0;i < m;i++) - out[i] = Clamp(out[i], ave - halfLim, ave + halfLim); + for(uint i{0};i < m;++i) + inout[i] = Clamp(inout[i], ave - halfLim, ave + halfLim); // Convert the response back to linear magnitude. - for(i = 0;i < m;i++) - out[i] = std::pow(10.0, out[i] / 20.0); + 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 @@ -289,22 +253,23 @@ void LimitMagnitudeResponse(const uint n, const uint m, const double limit, cons * residuals (which were discarded). The mirrored half of the response is * reconstructed. */ -void MinimumPhase(const uint n, double *mags, complex_d *out) +void MinimumPhase(const al::span mags, const al::span out) { - const uint m{(n/2) + 1}; + assert(mags.size() == out.size()); + const size_t m{(mags.size()/2) + 1}; - uint i; + size_t i; for(i = 0;i < m;i++) out[i] = std::log(mags[i]); - for(;i < n;i++) + for(;i < mags.size();++i) { - mags[i] = mags[n - i]; - out[i] = out[n - i]; + mags[i] = mags[mags.size() - i]; + out[i] = out[mags.size() - i]; } - Hilbert(n, out); + complex_hilbert(out); // Remove any DC offset the filter has. mags[0] = Epsilon; - for(i = 0;i < n;i++) + for(i = 0;i < mags.size();++i) out[i] = std::polar(mags[i], out[i].imag()); } @@ -314,11 +279,11 @@ void MinimumPhase(const uint n, double *mags, complex_d *out) ***************************/ // Write an ASCII string to a file. -int WriteAscii(const std::string_view out, FILE *fp, const char *filename) +auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int { - if(fwrite(out.data(), 1, out.size(), fp) != out.size()) + if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; @@ -326,54 +291,55 @@ int WriteAscii(const std::string_view out, FILE *fp, const char *filename) // Write a binary value of the given byte order and byte size to a file, // loading it from a 32-bit unsigned integer. -int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename) +auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream, + const std::string_view filename) -> int { - std::array out{}; + std::array out{}; for(uint i{0};i < bytes;i++) - out[i] = (in>>(i*8)) & 0x000000FF; + out[i] = static_cast((in>>(i*8)) & 0x000000FF); - if(fwrite(out.data(), 1, bytes, fp) != bytes) + if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; } // Store the OpenAL Soft HRTF data set. -bool StoreMhr(const HrirDataT *hData, const char *filename) +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}; - FilePtr fp{fopen(filename, "wb")}; - if(!fp) + auto ostream = fs::ofstream{fs::u8path(filename), std::ios::binary}; + if(!ostream.is_open()) { - fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename); + fmt::println(stderr, "\nError: Could not open MHR file '{}'.", filename); return false; } - if(!WriteAscii(GetMHRMarker(), fp.get(), filename)) + if(!WriteAscii(GetMHRMarker(), ostream, filename)) return false; - if(!WriteBin4(4, hData->mIrRate, fp.get(), filename)) + if(!WriteBin4(4, hData->mIrRate, ostream, filename)) return false; - if(!WriteBin4(1, static_cast(hData->mChannelType), fp.get(), filename)) + if(!WriteBin4(1, static_cast(hData->mChannelType), ostream, filename)) return false; - if(!WriteBin4(1, hData->mIrPoints, fp.get(), filename)) + if(!WriteBin4(1, hData->mIrPoints, ostream, filename)) return false; - if(!WriteBin4(1, static_cast(hData->mFds.size()), fp.get(), filename)) + 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, fp.get(), filename)) + if(!WriteBin4(2, fdist, ostream, filename)) return false; - if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), fp.get(), filename)) + 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()), fp.get(), filename)) + if(!WriteBin4(1, static_cast(elev.mAzs.size()), ostream, filename)) return false; } } @@ -389,14 +355,14 @@ bool StoreMhr(const HrirDataT *hData, const char *filename) { std::array out{}; - TpdfDither(out.data(), azd.mIrs[0], scale, n, channels, &dither_seed); + TpdfDither(out, azd.mIrs[0].first(n), scale, 0, channels, &dither_seed); if(hData->mChannelType == CT_STEREO) - TpdfDither(out.data()+1, azd.mIrs[1], scale, n, channels, &dither_seed); + TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed); const size_t numsamples{size_t{channels} * n}; for(size_t i{0};i < numsamples;i++) { const auto v = static_cast(Clamp(out[i], -scale-1.0, scale)); - if(!WriteBin4(bps, static_cast(v), fp.get(), filename)) + if(!WriteBin4(bps, static_cast(v), ostream, filename)) return false; } } @@ -411,11 +377,11 @@ bool StoreMhr(const HrirDataT *hData, const char *filename) for(const auto &azd : evd.mAzs) { auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); - if(!WriteBin4(1, v, fp.get(), filename)) return false; + 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, fp.get(), filename)) return false; + if(!WriteBin4(1, v, ostream, filename)) return false; } } } @@ -476,7 +442,7 @@ void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const u * on its coverage volume. All volumes are centered at the spherical HRIR * coordinates and measured by extruded solid angle. */ -void CalculateDfWeights(const HrirDataT *hData, double *weights) +void CalculateDfWeights(const HrirDataT *hData, const al::span weights) { double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; double solidAngle, solidVolume; @@ -533,7 +499,7 @@ void CalculateDfWeights(const HrirDataT *hData, double *weights) * 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, double *dfa) + const bool weighted, const double limit, const al::span dfa) { std::vector weights(hData->mFds.size() * MAX_EV_COUNT); uint count; @@ -541,7 +507,7 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c if(weighted) { // Use coverage weighting to calculate the average. - CalculateDfWeights(hData, weights.data()); + CalculateDfWeights(hData, weights); } else { @@ -589,13 +555,14 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c // Apply a limit to the magnitude range of the diffuse-field average // if desired. if(limit > 0.0) - LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]); + 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 double *dfa, const HrirDataT *hData) +void DiffuseFieldEqualize(const uint channels, const uint m, const al::span dfa, + const HrirDataT *hData) { for(size_t fi{0};fi < hData->mFds.size();++fi) { @@ -814,7 +781,7 @@ void SynthesizeHrirs(HrirDataT *hData) */ FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { @@ -854,7 +821,7 @@ void SynthesizeHrirs(HrirDataT *hData) } FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ti{0u};ti < channels;ti++) { @@ -872,7 +839,7 @@ void SynthesizeHrirs(HrirDataT *hData) * or more threads (sharing the same reconstructor object). */ struct HrirReconstructor { - std::vector mIrs; + std::vector> mIrs; std::atomic mCurrent{}; std::atomic mDone{}; uint mFftSize{}; @@ -904,7 +871,7 @@ struct HrirReconstructor { */ for(size_t i{0};i < m;++i) mags[i] = std::max(mIrs[idx][i], Epsilon); - MinimumPhase(mFftSize, mags.data(), h.data()); + MinimumPhase(mags, h); FftInverse(mFftSize, h.data()); for(uint i{0u};i < mIrPoints;++i) mIrs[idx][i] = h[i].real(); @@ -943,7 +910,7 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) - thrds.emplace_back(std::mem_fn(&HrirReconstructor::Worker), &reconstructor); + thrds.emplace_back(&HrirReconstructor::Worker, &reconstructor); /* Keep track of the number of IRs done, periodically reporting it. */ size_t count; @@ -953,10 +920,10 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) count = reconstructor.mDone.load(); size_t pcdone{count * 100 / reconstructor.mIrs.size()}; - printf("\r%3zu%% done (%zu of %zu)", pcdone, count, reconstructor.mIrs.size()); + fmt::print("\r{:3}% done ({} of {})", pcdone, count, reconstructor.mIrs.size()); fflush(stdout); } while(count < reconstructor.mIrs.size()); - fputc('\n', stdout); + fmt::println(""); for(auto &thrd : thrds) { @@ -973,10 +940,11 @@ void NormalizeHrirs(HrirDataT *hData) /* Find the maximum amplitude and RMS out of all the IRs. */ struct LevelPair { double amp, rms; }; - auto mesasure_channel = [irSize](const LevelPair levels, const double *ir) + auto mesasure_channel = [irSize](const LevelPair levels, al::span ir) { /* Calculate the peak amplitude and RMS of this IR. */ - auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, + ir = ir.first(irSize); + auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0}, [](const LevelPair cur, const double impulse) { return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse}; @@ -1011,8 +979,12 @@ void NormalizeHrirs(HrirDataT *hData) factor = std::min(factor, 0.99/maxlev.amp); /* Now scale all IRs by the given factor. */ - auto proc_channel = [irSize,factor](double *ir) - { std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); }; + auto proc_channel = [irSize,factor](al::span ir) + { + ir = ir.first(irSize); + std::transform(ir.cbegin(), ir.cend(), ir.begin(), + [factor](double s) { return s * factor; }); + }; auto proc_azi = [channels,proc_channel](HrirAzT &azi) { std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); }; auto proc_elev = [proc_azi](HrirEvT &elev) @@ -1101,7 +1073,7 @@ void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hDat } if(maxHrtd > MaxHrtd) { - fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", 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) { @@ -1145,7 +1117,7 @@ bool PrepareHrirData(const al::span distances, { hData->mFds[fi].mDistance = distances[fi]; hData->mFds[fi].mEvStart = 0; - hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]}; + hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]); evTotal += evCounts[fi]; for(uint ei{0};ei < evCounts[fi];++ei) { @@ -1153,15 +1125,15 @@ bool PrepareHrirData(const al::span distances, hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei / (evCounts[fi] - 1); - hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount}; + hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount); for(uint ai{0};ai < azCount;ai++) { hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * al::numbers::pi * ai / azCount; hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0; - hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = nullptr; - hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = nullptr; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {}; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {}; } azTotal += azCount; } @@ -1176,36 +1148,35 @@ namespace { * resulting data set as desired. If the input name is NULL it will read * from standard input. */ -bool ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode, +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 char *outName) + const double radius, const std::string_view outName) { HrirDataT hData; - fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s"); - if(!inName) + fmt::println("Using {} thread{}.", numThreads, (numThreads==1)?"":"s"); + if(inName.empty() || inName == "-"sv) { - inName = "stdin"; - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData)) + 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(std::filesystem::u8path(inName)); + auto input = std::make_unique(fs::u8path(inName)); if(!input->is_open()) { - fprintf(stderr, "Error: Could not open input file '%s'\n", inName); + fmt::println(stderr, "Error: Could not open input file '{}'", inName); return false; } std::array startbytes{}; input->read(startbytes.data(), startbytes.size()); - std::streamsize startbytecount{input->gcount()}; - if(startbytecount != startbytes.size() || !input->good()) + if(input->gcount() != startbytes.size() || !input->good()) { - fprintf(stderr, "Error: Could not read input file '%s'\n", inName); + fmt::println(stderr, "Error: Could not read input file '{}'", inName); return false; } @@ -1213,15 +1184,15 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode && startbytes[3] == 'F') { input = nullptr; - fprintf(stdout, "Reading HRTF data from %s...\n", inName); + fmt::println("Reading HRTF data from {}...", inName); if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) return false; } else { - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(*input, startbytes.data(), startbytecount, inName, fftSize, truncSize, - outRate, chanMode, &hData)) + fmt::println("Reading HRIR definition from {}...", inName); + if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode, + &hData)) return false; } } @@ -1234,85 +1205,82 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode if(hData.mFds.size() > 1) { - fprintf(stdout, "Balancing field magnitudes...\n"); + fmt::println("Balancing field magnitudes..."); BalanceFieldMagnitudes(&hData, c, m); } - fprintf(stdout, "Calculating diffuse-field average...\n"); - CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data()); - fprintf(stdout, "Performing diffuse-field equalization...\n"); - DiffuseFieldEqualize(c, m, dfa.data(), &hData); + 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) { - fprintf(stdout, "Sorting %zu fields...\n", hData.mFds.size()); + fmt::println("Sorting {} fields...", hData.mFds.size()); std::sort(hData.mFds.begin(), hData.mFds.end(), [](const HrirFdT &lhs, const HrirFdT &rhs) noexcept { return lhs.mDistance < rhs.mDistance; }); if(farfield) { - fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1, + 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); } } - fprintf(stdout, "Synthesizing missing elevations...\n"); + fmt::println("Synthesizing missing elevations..."); if(model == HM_Dataset) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); - fprintf(stdout, "Performing minimum phase reconstruction...\n"); + fmt::println("Performing minimum phase reconstruction..."); ReconstructHrirs(&hData, numThreads); - fprintf(stdout, "Truncating minimum-phase HRIRs...\n"); + fmt::println("Truncating minimum-phase HRIRs..."); hData.mIrPoints = truncSize; - fprintf(stdout, "Normalizing final HRIRs...\n"); + fmt::println("Normalizing final HRIRs..."); NormalizeHrirs(&hData); - fprintf(stdout, "Calculating impulse delays...\n"); + 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, strlen(outName)}, {"%r", 2}, - {rateStr.data(), rateStr.size()}); - fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str()); - return StoreMhr(&hData, expName.c_str()); + const auto expName = StrSubst(outName, "%r"sv, rateStr); + fmt::println("Creating MHR data set {}...", expName); + return StoreMhr(&hData, expName); } -void PrintHelp(const char *argv0, FILE *ofile) +void PrintHelp(const std::string_view argv0, FILE *ofile) { - fprintf(ofile, "Usage: %s [