ソースを参照

update OpenAL-Soft to 1.24.3.

Sasha Szpakowski 7 ヶ月 前
コミット
5e4f3241ac
100 ファイル変更7929 行追加5572 行削除
  1. 1 1
      CMakeLists.txt
  2. 180 0
      libs/openal-soft/.clang-tidy
  3. 98 8
      libs/openal-soft/.github/workflows/ci.yml
  4. 26 17
      libs/openal-soft/.github/workflows/utils.yml
  5. 284 191
      libs/openal-soft/CMakeLists.txt
  6. 134 0
      libs/openal-soft/ChangeLog
  7. 11 0
      libs/openal-soft/README.md
  8. 237 268
      libs/openal-soft/al/auxeffectslot.cpp
  9. 72 66
      libs/openal-soft/al/auxeffectslot.h
  10. 266 220
      libs/openal-soft/al/buffer.cpp
  11. 4 2
      libs/openal-soft/al/buffer.h
  12. 159 145
      libs/openal-soft/al/debug.cpp
  13. 49 47
      libs/openal-soft/al/eax/api.h
  14. 30 24
      libs/openal-soft/al/eax/call.cpp
  15. 35 20
      libs/openal-soft/al/eax/effect.h
  16. 7 2
      libs/openal-soft/al/eax/exception.h
  17. 1 4
      libs/openal-soft/al/eax/fx_slots.h
  18. 2 3
      libs/openal-soft/al/eax/utils.cpp
  19. 3 69
      libs/openal-soft/al/eax/utils.h
  20. 0 5
      libs/openal-soft/al/eax/x_ram.h
  21. 150 133
      libs/openal-soft/al/effect.cpp
  22. 10 2
      libs/openal-soft/al/effect.h
  23. 35 44
      libs/openal-soft/al/effects/autowah.cpp
  24. 106 95
      libs/openal-soft/al/effects/chorus.cpp
  25. 28 38
      libs/openal-soft/al/effects/compressor.cpp
  26. 27 68
      libs/openal-soft/al/effects/convolution.cpp
  27. 52 59
      libs/openal-soft/al/effects/dedicated.cpp
  28. 41 45
      libs/openal-soft/al/effects/distortion.cpp
  29. 40 40
      libs/openal-soft/al/effects/echo.cpp
  30. 49 58
      libs/openal-soft/al/effects/effects.h
  31. 54 60
      libs/openal-soft/al/effects/equalizer.cpp
  32. 41 46
      libs/openal-soft/al/effects/fshifter.cpp
  33. 46 45
      libs/openal-soft/al/effects/modulator.cpp
  34. 24 55
      libs/openal-soft/al/effects/null.cpp
  35. 30 38
      libs/openal-soft/al/effects/pshifter.cpp
  36. 192 181
      libs/openal-soft/al/effects/reverb.cpp
  37. 54 61
      libs/openal-soft/al/effects/vmorpher.cpp
  38. 25 53
      libs/openal-soft/al/error.cpp
  39. 0 27
      libs/openal-soft/al/error.h
  40. 31 29
      libs/openal-soft/al/event.cpp
  41. 0 2
      libs/openal-soft/al/extension.cpp
  42. 185 158
      libs/openal-soft/al/filter.cpp
  43. 15 9
      libs/openal-soft/al/filter.h
  44. 90 55
      libs/openal-soft/al/listener.cpp
  45. 228 212
      libs/openal-soft/al/source.cpp
  46. 51 37
      libs/openal-soft/al/source.h
  47. 86 53
      libs/openal-soft/al/state.cpp
  48. 260 166
      libs/openal-soft/alc/alc.cpp
  49. 93 61
      libs/openal-soft/alc/alconfig.cpp
  50. 273 249
      libs/openal-soft/alc/alu.cpp
  51. 7 4
      libs/openal-soft/alc/alu.h
  52. 116 126
      libs/openal-soft/alc/backends/alsa.cpp
  53. 5 5
      libs/openal-soft/alc/backends/alsa.h
  54. 41 23
      libs/openal-soft/alc/backends/base.cpp
  55. 33 14
      libs/openal-soft/alc/backends/base.h
  56. 171 100
      libs/openal-soft/alc/backends/coreaudio.cpp
  57. 6 6
      libs/openal-soft/alc/backends/coreaudio.h
  58. 116 130
      libs/openal-soft/alc/backends/dsound.cpp
  59. 5 5
      libs/openal-soft/alc/backends/dsound.h
  60. 132 107
      libs/openal-soft/alc/backends/jack.cpp
  61. 5 5
      libs/openal-soft/alc/backends/jack.h
  62. 4 4
      libs/openal-soft/alc/backends/loopback.cpp
  63. 5 5
      libs/openal-soft/alc/backends/loopback.h
  64. 19 21
      libs/openal-soft/alc/backends/null.cpp
  65. 5 5
      libs/openal-soft/alc/backends/null.h
  66. 35 34
      libs/openal-soft/alc/backends/oboe.cpp
  67. 5 5
      libs/openal-soft/alc/backends/oboe.h
  68. 131 72
      libs/openal-soft/alc/backends/opensl.cpp
  69. 5 5
      libs/openal-soft/alc/backends/opensl.h
  70. 72 79
      libs/openal-soft/alc/backends/oss.cpp
  71. 5 5
      libs/openal-soft/alc/backends/oss.h
  72. 700 0
      libs/openal-soft/alc/backends/otherio.cpp
  73. 21 0
      libs/openal-soft/alc/backends/otherio.h
  74. 163 155
      libs/openal-soft/alc/backends/pipewire.cpp
  75. 8 6
      libs/openal-soft/alc/backends/pipewire.h
  76. 229 116
      libs/openal-soft/alc/backends/portaudio.cpp
  77. 0 19
      libs/openal-soft/alc/backends/portaudio.h
  78. 19 0
      libs/openal-soft/alc/backends/portaudio.hpp
  79. 291 223
      libs/openal-soft/alc/backends/pulseaudio.cpp
  80. 12 6
      libs/openal-soft/alc/backends/pulseaudio.h
  81. 110 77
      libs/openal-soft/alc/backends/sdl2.cpp
  82. 5 5
      libs/openal-soft/alc/backends/sdl2.h
  83. 393 0
      libs/openal-soft/alc/backends/sdl3.cpp
  84. 19 0
      libs/openal-soft/alc/backends/sdl3.h
  85. 51 57
      libs/openal-soft/alc/backends/sndio.cpp
  86. 0 19
      libs/openal-soft/alc/backends/sndio.h
  87. 19 0
      libs/openal-soft/alc/backends/sndio.hpp
  88. 26 26
      libs/openal-soft/alc/backends/solaris.cpp
  89. 5 5
      libs/openal-soft/alc/backends/solaris.h
  90. 634 475
      libs/openal-soft/alc/backends/wasapi.cpp
  91. 6 6
      libs/openal-soft/alc/backends/wasapi.h
  92. 50 45
      libs/openal-soft/alc/backends/wave.cpp
  93. 5 5
      libs/openal-soft/alc/backends/wave.h
  94. 75 93
      libs/openal-soft/alc/backends/winmm.cpp
  95. 5 5
      libs/openal-soft/alc/backends/winmm.h
  96. 123 105
      libs/openal-soft/alc/context.cpp
  97. 65 45
      libs/openal-soft/alc/context.h
  98. 18 14
      libs/openal-soft/alc/device.cpp
  99. 37 32
      libs/openal-soft/alc/device.h
  100. 2 2
      libs/openal-soft/alc/effects/autowah.cpp

+ 1 - 1
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)

+ 180 - 0
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-*'

+ 98 - 8
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/[email protected]
-      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/[email protected]
+      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/[email protected]"
+      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/*"

+ 26 - 17
libs/openal-soft/.github/workflows/makemhr.yml → 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/[email protected]
@@ -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/[email protected]
+    - 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"

+ 284 - 191
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 <shlobj.h>
+            #include <knownfolders.h>
+            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} $<$<COMPILE_LANGUAGE:CXX>:/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
+        $<$<COMPILE_LANGUAGE:CXX>:/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()
 
+    check_cxx_compiler_flag(-Wno-interference-size HAVE_WNO_INTERFERENCE_SIZE)
+    if(HAVE_WNO_INTERFERENCE_SIZE)
+        set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:-Wno-interference-size>)
+    endif()
+
     if(ALSOFT_WERROR)
         set(C_FLAGS ${C_FLAGS} -Werror)
+    else()
+        set(C_FLAGS ${C_FLAGS} -Werror=undef)
     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()
+    # 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}
+        $<BUILD_LOCAL_INTERFACE:alsoft::fmt>)
 
     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
             $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
@@ -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.")
-        endif()
+        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\"")
+            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)
+            set_target_properties(${IMPL_TARGET} PROPERTIES
+                VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props
+            )
+            target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets)
+        endif()
     endif()
 
     if(NOT WIN32 AND NOT APPLE)
@@ -1473,8 +1555,6 @@ else()
     endif()
 endif()
 
-set(OPENAL_LIB_NAME ${IMPL_TARGET} PARENT_SCOPE)
-
 target_include_directories(${IMPL_TARGET}
   PUBLIC
     $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
@@ -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()

+ 134 - 0
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.

+ 11 - 0
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.
 

ファイルの差分が大きいため隠しています
+ 237 - 268
libs/openal-soft/al/auxeffectslot.cpp


+ 72 - 66
libs/openal-soft/al/auxeffectslot.h

@@ -1,8 +1,11 @@
 #ifndef AL_AUXEFFECTSLOT_H
 #define AL_AUXEFFECTSLOT_H
 
+#include "config.h"
+
 #include <array>
 #include <atomic>
+#include <bitset>
 #include <cstdint>
 #include <string_view>
 #include <utility>
@@ -16,7 +19,7 @@
 #include "core/effectslot.h"
 #include "intrusive_ptr.h"
 
-#ifdef ALSOFT_EAX
+#if ALSOFT_EAX
 #include <memory>
 #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<EffectState> 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<const EAX40FXSLOTPROPERTIES&>(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<eax_dirty_bit_count> 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<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
-    static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags)
+    template<typename TValidator, size_t DirtyBit, typename TProperties>
+    static void eax_fx_slot_set(const EaxCall& call, TProperties& dst,
+        std::bitset<eax_dirty_bit_count>& dirty_flags)
     {
         const auto& src = call.get_value<Exception, const TProperties>();
         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<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
+    template<typename TValidator, size_t DirtyBit, typename TProperties>
     static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst,
-        EaxDirtyFlags& dirty_flags)
+        std::bitset<eax_dirty_bit_count>& dirty_flags)
     {
         const auto& src = call.get_value<Exception, const TProperties>();
         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<eax_dirty_bit_count>& 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<eax_dirty_bit_count>& dst_df);
+    void eax5_fx_slot_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& 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<ALeffectslot, ALeffectslot::EaxDeleter>;
 
 EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context);

ファイルの差分が大きいため隠しています
+ 266 - 220
libs/openal-soft/al/buffer.cpp


+ 4 - 2
libs/openal-soft/al/buffer.h

@@ -1,6 +1,8 @@
 #ifndef AL_BUFFER_H
 #define AL_BUFFER_H
 
+#include "config.h"
+
 #include <array>
 #include <atomic>
 #include <cstddef>
@@ -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

+ 159 - 145
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<typename T, T ...Vals>
@@ -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 "<invalid source>";
+    return "<invalid source>"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 "<invalid type>";
+    return "<invalid type>"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 "<invalid severity>";
+    return "<invalid severity>"sv;
 }
 
 } // namespace
@@ -195,8 +199,8 @@ void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &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<std::mutex> &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<uint>(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<uint8_t,ElemCount>();
@@ -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<<idx); });
     }
 }
-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());
 }
 
 
@@ -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<ALsizei>(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<std::mutex> 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<uint>(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<std::mutex> 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"};
-
-    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<ALuint>(logBufSize) : 0u};
-
-    std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
+    if(logBuf && logBufSize < 0)
+        context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size");
+
+    const auto sourcesSpan = al::span{sources, sources ? count : 0u};
+    const auto typesSpan = al::span{types, types ? count : 0u};
+    const auto idsSpan = al::span{ids, ids ? count : 0u};
+    const auto severitiesSpan = al::span{severities, severities ? count : 0u};
+    const auto lengthsSpan = al::span{lengths, lengths ? count : 0u};
+    const auto logSpan = al::span{logBuf, logBuf ? static_cast<ALuint>(logBufSize) : 0u};
+
+    auto sourceiter = sourcesSpan.begin();
+    auto typeiter = typesSpan.begin();
+    auto iditer = idsSpan.begin();
+    auto severityiter = severitiesSpan.begin();
+    auto lengthiter = lengthsSpan.begin();
+    auto logiter = logSpan.begin();
+
+    auto debuglock = std::lock_guard{context->mDebugCbLock};
     for(ALuint i{0};i < count;++i)
     {
         if(context->mDebugLog.empty())
             return i;
 
         auto &entry = context->mDebugLog.front();
-        const 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<size_t>(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<ALsizei>(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<ALsizei>(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<uint>(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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<ALuint>(bufSize) : 0u};
     auto copy_name = [name,length,labelOut](std::unordered_map<ALuint,std::string> &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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+catch(std::exception &e) {
+    ERR("Caught exception: {}", e.what());
 }

+ 49 - 47
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<EAX_REVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
-extern const Eax1ReverbPresets EAX1REVERB_PRESETS;
+DECL_HIDDEN extern const Eax1ReverbPresets EAX1REVERB_PRESETS;
 
 using Eax2ReverbPresets = std::array<EAX20LISTENERPROPERTIES, EAX2_ENVIRONMENT_COUNT>;
-extern const Eax2ReverbPresets EAX2REVERB_PRESETS;
+DECL_HIDDEN extern const Eax2ReverbPresets EAX2REVERB_PRESETS;
 
 using EaxReverbPresets = std::array<EAXREVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
-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,

+ 30 - 24
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)
-    {
-    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;
+    if(mPropertySetId == EaxCallPropertySetId::context)
+    {
+        switch(mPropertyId)
+        {
+        case EAXCONTEXT_LASTERROR:
+        case EAXCONTEXT_SPEAKERCONFIG:
+        case EAXCONTEXT_EAXSESSION:
+            // EAX allow to set "defer" flag on immediate-only properties.
+            // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called.
+            mIsDeferred = false;
+            break;
+        }
+    }
+    else if(mPropertySetId == EaxCallPropertySetId::fx_slot)
+    {
+        switch(mPropertyId)
+        {
+        case EAXFXSLOT_NONE:
+        case EAXFXSLOT_ALLPARAMETERS:
+        case EAXFXSLOT_LOADEFFECT:
+        case EAXFXSLOT_VOLUME:
+        case EAXFXSLOT_LOCK:
+        case EAXFXSLOT_FLAGS:
+        case EAXFXSLOT_OCCLUSION:
+        case EAXFXSLOT_OCCLUSIONLFRATIO:
+            mIsDeferred = false;
+            break;
+        }
     }
 
     if(!mIsDeferred)

+ 35 - 20
libs/openal-soft/al/eax/effect.h

@@ -1,17 +1,17 @@
 #ifndef EAX_EFFECT_INCLUDED
 #define EAX_EFFECT_INCLUDED
 
-
 #include <cassert>
 #include <memory>
 #include <variant>
 
-#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<typename T>
 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<EaxAutowahCommitter> {
-    using EaxCommitter<EaxAutowahCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxAutowahCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXAUTOWAHPROPERTIES &props);
 
@@ -153,7 +157,8 @@ struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
     static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props);
 };
 struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
-    using EaxCommitter<EaxChorusCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxChorusCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXCHORUSPROPERTIES &props);
 
@@ -162,7 +167,8 @@ struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
     static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props);
 };
 struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
-    using EaxCommitter<EaxCompressorCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxCompressorCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXAGCCOMPRESSORPROPERTIES &props);
 
@@ -171,7 +177,8 @@ struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
     static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props);
 };
 struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
-    using EaxCommitter<EaxDistortionCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxDistortionCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXDISTORTIONPROPERTIES &props);
 
@@ -180,7 +187,8 @@ struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
     static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props);
 };
 struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
-    using EaxCommitter<EaxEchoCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxEchoCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXECHOPROPERTIES &props);
 
@@ -189,7 +197,8 @@ struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
     static void Set(const EaxCall &call, EAXECHOPROPERTIES &props);
 };
 struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
-    using EaxCommitter<EaxEqualizerCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxEqualizerCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXEQUALIZERPROPERTIES &props);
 
@@ -198,7 +207,8 @@ struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
     static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props);
 };
 struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
-    using EaxCommitter<EaxFlangerCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxFlangerCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXFLANGERPROPERTIES &props);
 
@@ -207,7 +217,8 @@ struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
     static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props);
 };
 struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> {
-    using EaxCommitter<EaxFrequencyShifterCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxFrequencyShifterCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props);
 
@@ -216,7 +227,8 @@ struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCom
     static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props);
 };
 struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
-    using EaxCommitter<EaxModulatorCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxModulatorCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXRINGMODULATORPROPERTIES &props);
 
@@ -225,7 +237,8 @@ struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
     static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props);
 };
 struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> {
-    using EaxCommitter<EaxPitchShifterCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxPitchShifterCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXPITCHSHIFTERPROPERTIES &props);
 
@@ -234,7 +247,8 @@ struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter>
     static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props);
 };
 struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> {
-    using EaxCommitter<EaxVocalMorpherCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxVocalMorpherCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(args)...} { }
 
     bool commit(const EAXVOCALMORPHERPROPERTIES &props);
 
@@ -243,7 +257,8 @@ struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter>
     static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props);
 };
 struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> {
-    using EaxCommitter<EaxNullCommitter>::EaxCommitter;
+    template<typename ...Args>
+    explicit EaxNullCommitter(Args&& ...args) : EaxCommitter{std::forward<Args>(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_{};

+ 7 - 2
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 */

+ 1 - 4
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);
 

+ 2 - 3
libs/openal-soft/al/eax/utils.cpp

@@ -5,7 +5,6 @@
 #include <cassert>
 #include <exception>
 
-#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.");
     }
 }

+ 3 - 69
libs/openal-soft/al/eax/utils.h

@@ -1,15 +1,11 @@
 #ifndef EAX_UTILS_INCLUDED
 #define EAX_UTILS_INCLUDED
 
-#include <algorithm>
-#include <cstdint>
-#include <string>
 #include <string_view>
-#include <type_traits>
 
+#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<typename T>
-struct EaxIsBitFieldStruct {
-private:
-    using yes = std::true_type;
-    using no = std::false_type;
-
-    template<typename U>
-    static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
-
-    template<typename>
-    static no test(...);
-
-public:
-    static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
-};
-
-template<typename T, typename TValue>
-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<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
-}
-
-} // namespace detail
-
-template<
-    typename T,
-    std::enable_if_t<detail::EaxIsBitFieldStruct<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, void>::value, "Unsupported type.");
-    return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
-}
-
-template<
-    typename T,
-    std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
->
-inline bool operator!=(const T& lhs, const T& rhs) noexcept
-{
-    return !(lhs == rhs);
-}
-
 #endif // !EAX_UTILS_INCLUDED

+ 0 - 5
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

+ 150 - 133
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<NullEffectHandler>(); break;
+    case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace<ReverbEffectHandler>(); break;
+    case AL_EFFECT_REVERB: effect->PropsVariant.emplace<StdReverbEffectHandler>(); break;
+    case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace<AutowahEffectHandler>(); break;
+    case AL_EFFECT_CHORUS: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
+    case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace<CompressorEffectHandler>(); break;
+    case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace<DistortionEffectHandler>(); break;
+    case AL_EFFECT_ECHO: effect->PropsVariant.emplace<EchoEffectHandler>(); break;
+    case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace<EqualizerEffectHandler>(); break;
+    case AL_EFFECT_FLANGER: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
+    case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace<FshifterEffectHandler>(); break;
+    case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace<ModulatorEffectHandler>(); break;
+    case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace<PshifterEffectHandler>(); break;
+    case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace<VmorpherEffectHandler>(); break;
+    case AL_EFFECT_DEDICATED_DIALOGUE:
+        effect->PropsVariant.emplace<DedicatedDialogEffectHandler>();
+        break;
+    case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT:
+        effect->PropsVariant.emplace<DedicatedLfeEffectHandler>();
+        break;
+    case AL_EFFECT_CONVOLUTION_SOFT:
+        effect->PropsVariant.emplace<ConvolutionEffectHandler>();
+        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<std::mutex> effectlock{device->EffectLock};
+    auto *device = context->mALDevice.get();
+    auto effectlock = std::lock_guard{device->EffectLock};
 
     const al::span eids{effects, static_cast<ALuint>(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<std::mutex> 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<ALuint>(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<std::mutex> 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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(aleffect->Props), param, value);
+    }, aleffect->PropsVariant);
+}
+catch(al::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(aleffect->Props), param, value);
+    }, aleffect->PropsVariant);
+}
+catch(al::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(aleffect->Props), param, value);
+    }, aleffect->PropsVariant);
+}
+catch(al::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(aleffect->Props), param, value);
+    }, aleffect->PropsVariant);
+}
+catch(al::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
-        if constexpr(std::is_same_v<Type,ReverbProps>)
-        {
-            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<PropType>(aleffect->Props), param, values);
+    }, aleffect->PropsVariant);
+}
+catch(al::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<ReverbProps>(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

+ 10 - 2
libs/openal-soft/al/effect.h

@@ -6,6 +6,7 @@
 #include <cstdint>
 #include <string_view>
 #include <utility>
+#include <variant>
 
 #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<EffectList,16> gEffectList;
+DECL_HIDDEN extern const std::array<EffectList,16> gEffectList;
 
+using EffectHandlerVariant = std::variant<NullEffectHandler,ReverbEffectHandler,
+    StdReverbEffectHandler,AutowahEffectHandler,ChorusEffectHandler,CompressorEffectHandler,
+    DistortionEffectHandler,EchoEffectHandler,EqualizerEffectHandler,FlangerEffectHandler,
+    FshifterEffectHandler,ModulatorEffectHandler,PshifterEffectHandler,VmorpherEffectHandler,
+    DedicatedDialogEffectHandler,DedicatedLfeEffectHandler,ConvolutionEffectHandler>;
 
 struct ALeffect {
     // Effect type (AL_EFFECT_NULL, ...)
     ALenum type{AL_EFFECT_NULL};
 
-    EffectProps Props{};
+    EffectHandlerVariant PropsVariant;
+    EffectProps Props;
 
     /* Self ID */
     ALuint id{0u};

+ 35 - 44
libs/openal-soft/al/effects/autowah.cpp

@@ -4,15 +4,13 @@
 #include <cmath>
 #include <cstdlib>
 
-#include <algorithm>
-
 #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<EaxAutowahCommitter>;

+ 106 - 95
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 <cassert>
-#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<int>(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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
-{ SetParamf(props, param, *vals); }
+void ChorusEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
 
-void EffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val)
+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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamfv(FlangerProps &props, ALenum param, const float *vals)
-{ SetParamf(props, param, *vals); }
+void FlangerEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
 
-void EffectHandler::GetParami(const FlangerProps &props, ALenum param, int *val)
+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<EaxProps>(&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<EaxFlangerTraits>;
-    return Committer::Commit(props, mEaxProps, mAlProps.emplace<FlangerProps>());
+    return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
 }
 
 void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)

+ 28 - 38
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<EaxCompressorCommitter>;

+ 27 - 68
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<const float> 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;
     }
-}
 
-void EffectHandler::GetParami(const 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::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals)
-{
-    switch(param)
-    {
-    default:
-        GetParami(props, param, vals);
-    }
+    SetParamf(context, props, param, *values);
 }
-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)
+
+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)
 {
-    al::span<float> 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);
 }

+ 52 - 59
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); }

+ 41 - 45
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<EaxDistortionCommitter>;

+ 40 - 40
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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}",
+        as_unsigned(param));
 }
-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)
+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<EaxEchoCommitter>;

+ 49 - 58
libs/openal-soft/al/effects/effects.h

@@ -3,71 +3,62 @@
 
 #include <variant>
 
+#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 */

+ 54 - 60
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<EaxEqualizerCommitter>;

+ 41 - 46
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 <cassert>
-#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<int>(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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals)
-{ SetParami(props, param, *vals); }
+void FshifterEffectHandler::SetParamiv(ALCcontext *context, FshifterProps &props, ALenum param, const int *vals)
+{ SetParami(context, props, param, *vals); }
 
-void EffectHandler::SetParamf(FshifterProps &props, ALenum param, float val)
+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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals)
-{ SetParamf(props, param, *vals); }
+void FshifterEffectHandler::SetParamfv(ALCcontext *context, FshifterProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
 
-void EffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val)
+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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals)
-{ GetParami(props, param, vals); }
+void FshifterEffectHandler::GetParamiv(ALCcontext *context, const FshifterProps &props, ALenum param, int *vals)
+{ GetParami(context, props, param, vals); }
 
-void EffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val)
+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<EaxFrequencyShifterCommitter>;

+ 46 - 45
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 <cassert>
-#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<int>(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<float>(val));
-        break;
+        SetParamf(context, props, param, static_cast<float>(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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals)
-{ SetParami(props, param, *vals); }
+void ModulatorEffectHandler::SetParamiv(ALCcontext *context, ModulatorProps &props, ALenum param, const int *vals)
+{ SetParami(context, props, param, *vals); }
 
-void EffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val)
+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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals)
-{ SetParamf(props, param, *vals); }
+void ModulatorEffectHandler::SetParamfv(ALCcontext *context, ModulatorProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
 
-void EffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val)
+void ModulatorEffectHandler::GetParami(ALCcontext *context, const ModulatorProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_RING_MODULATOR_FREQUENCY: *val = static_cast<int>(props.Frequency); break;
-    case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(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<int>(props.Frequency); return;
+    case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(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<EaxModulatorCommitter>;

+ 24 - 55
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<EaxNullCommitter>;

+ 30 - 38
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;
     }
-}
-void EffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals)
-{ GetParami(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};
+    context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}",
+        as_unsigned(param));
 }
+void PshifterEffectHandler::GetParamiv(ALCcontext *context, const PshifterProps &props, ALenum param, int *vals)
+{ GetParami(context, props, param, vals); }
+
+void PshifterEffectHandler::GetParamf(ALCcontext *context, const PshifterProps&, ALenum param, float*)
+{ context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); }
+void PshifterEffectHandler::GetParamfv(ALCcontext *context, const PshifterProps &props, ALenum param, float *vals)
+{ GetParamf(context, props, param, vals); }
 
 
-#ifdef ALSOFT_EAX
+#if ALSOFT_EAX
 namespace {
 
 using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>;

+ 192 - 181
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 <cassert>
+
 #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<const float> 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<float> 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};
-    }
-}
-void EffectHandler::StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals)
-{
-    switch(param)
-    {
-    default:
-        StdReverbSetParamf(props, param, *vals);
-        break;
+        return;
     }
-}
 
-void EffectHandler::StdReverbGetParami(const ReverbProps &props, ALenum param, int *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};
-    }
+    context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals)
-{ StdReverbGetParami(props, param, vals); }
-void EffectHandler::StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val)
+void StdReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
+
+void StdReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_REVERB_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};
+    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::StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals)
+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)
     {
-    default:
-        StdReverbGetParamf(props, param, vals);
-        break;
+    case AL_REVERB_DENSITY: *val = props.Density; return;
+    case AL_REVERB_DIFFUSION: *val = props.Diffusion; return;
+    case AL_REVERB_GAIN: *val = props.Gain; return;
+    case AL_REVERB_GAINHF: *val = props.GainHF; return;
+    case AL_REVERB_DECAY_TIME: *val = props.DecayTime; return;
+    case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return;
+    case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return;
+    case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return;
+    case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return;
+    case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return;
+    case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return;
+    case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}",
+        as_unsigned(param));
 }
+void StdReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals)
+{ GetParamf(context, props, param, vals); }
 
 
-#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;
     }();
 

+ 54 - 61
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 <cassert>
-#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<int>(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<int>(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;
     }
+
+    context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}",
+        as_unsigned(param));
 }
-void EffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
-{ SetParamf(props, param, *vals); }
+void VmorpherEffectHandler::SetParamfv(ALCcontext *context, VmorpherProps &props, ALenum param, const float *vals)
+{ SetParamf(context, props, param, *vals); }
 
-void EffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val)
+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<EaxVocalMorpherCommitter>;

+ 25 - 53
libs/openal-soft/al/error.cpp

@@ -20,24 +20,19 @@
 
 #include "config.h"
 
-#include "error.h"
-
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 #endif
 
-#include <atomic>
 #include <csignal>
 #include <cstdarg>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <limits>
 #include <optional>
 #include <string>
 #include <utility>
-#include <vector>
 
 #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<char>(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<size_t>(msglen) >= message.size())
-    {
-        message.resize(static_cast<size_t>(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 = "<internal error constructing message>";
-        msglen = static_cast<int>(strlen(msg));
-    }
-
-    WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
-        decltype(std::declval<void*>()){this}, errorCode, msg);
+    WARN("Error generated on context {}, code {:#04x}, \"{}\"",
+        decltype(std::declval<void*>()){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<uint>(msglen)});
+    debugMessage(DebugSource::API, DebugType::Error, static_cast<ALuint>(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<ALenum>::max())
-                return static_cast<ALenum>(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<ALenum>(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

+ 0 - 27
libs/openal-soft/al/error.h

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

+ 31 - 29
libs/openal-soft/al/event.cpp

@@ -3,7 +3,6 @@
 
 #include "event.h"
 
-#include <array>
 #include <atomic>
 #include <bitset>
 #include <exception>
@@ -12,10 +11,8 @@
 #include <new>
 #include <optional>
 #include <string>
-#include <string_view>
 #include <thread>
 #include <tuple>
-#include <utility>
 #include <variant>
 
 #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<std::mutex> 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<AsyncEvent*>(evt_data.buf)),
             evt_data.len};
         for(auto &event : evt_span)
@@ -67,7 +66,6 @@ int EventThread(ALCcontext *context)
             quitnow = std::holds_alternative<AsyncKillThread>(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<ALsizei>(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<ALsizei>(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()};
-
-                context->debugMessage(DebugSource::System, DebugType::Error, 0,
-                    DebugSeverity::High, message);
+                if(!context->mEventCb
+                    || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected)))
+                    return;
 
-                if(context->mEventCb
-                    && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected)))
-                    context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
-                        static_cast<ALsizei>(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<AsyncKillThread>(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<uint>(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<std::mutex> 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<std::mutex> eventlock{context->mEventCbLock};
     context->mEventCb = callback;
     context->mEventParam = userParam;
 }
+catch(al::base_exception&) {
+}
+catch(std::exception &e) {
+    ERR("Caught exception: {}", e.what());
+}

+ 0 - 2
libs/openal-soft/al/extension.cpp

@@ -21,13 +21,11 @@
 #include "config.h"
 
 #include <string_view>
-#include <vector>
 
 #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"

+ 185 - 158
libs/openal-soft/al/filter.cpp

@@ -30,7 +30,6 @@
 #include <memory>
 #include <mutex>
 #include <numeric>
-#include <string>
 #include <unordered_map>
 #include <vector>
 
@@ -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<NullFilterTable>::setParami(ALfilter*, ALenum param, int)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::setParami(ALCcontext *context, ALfilter*, ALenum param, int)
+{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
 template<>
-void FilterTable<NullFilterTable>::setParamiv(ALfilter*, ALenum param, const int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<NullFilterTable>::setParamf(ALfilter*, ALenum param, float)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::setParamf(ALCcontext *context, ALfilter*, ALenum param, float)
+{ context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); }
 template<>
-void FilterTable<NullFilterTable>::setParamfv(ALfilter*, ALenum param, const float*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<NullFilterTable>::getParami(const ALfilter*, ALenum param, int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<NullFilterTable>::getParamiv(const ALfilter*, ALenum param, int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<NullFilterTable>::getParamf(const ALfilter*, ALenum param, float*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<NullFilterTable>::getParamfv(const ALfilter*, ALenum param, float*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+void FilterTable<NullFilterTable>::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<LowpassFilterTable>::setParami(ALfilter*, ALenum param, int)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
+void FilterTable<LowpassFilterTable>::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<LowpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
-{ setParami(filter, param, *values); }
+void FilterTable<LowpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
+{ setParami(context, filter, param, *values); }
 template<>
-void FilterTable<LowpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val)
+void FilterTable<LowpassFilterTable>::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<LowpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ setParamf(filter, param, *vals); }
+void FilterTable<LowpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(context, filter, param, *vals); }
 template<>
-void FilterTable<LowpassFilterTable>::getParami(const ALfilter*, ALenum param, int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
+void FilterTable<LowpassFilterTable>::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<LowpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
-{ getParami(filter, param, values); }
+void FilterTable<LowpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
+{ getParami(context, filter, param, values); }
 template<>
-void FilterTable<LowpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val)
+void FilterTable<LowpassFilterTable>::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<LowpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ getParamf(filter, param, vals); }
+void FilterTable<LowpassFilterTable>::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals)
+{ getParamf(context, filter, param, vals); }
 
 /* Highpass parameter handlers */
 template<>
-void FilterTable<HighpassFilterTable>::setParami(ALfilter*, ALenum param, int)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
+void FilterTable<HighpassFilterTable>::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<HighpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
-{ setParami(filter, param, *values); }
+void FilterTable<HighpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
+{ setParami(context, filter, param, *values); }
 template<>
-void FilterTable<HighpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val)
+void FilterTable<HighpassFilterTable>::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<HighpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ setParamf(filter, param, *vals); }
+void FilterTable<HighpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(context, filter, param, *vals); }
 template<>
-void FilterTable<HighpassFilterTable>::getParami(const ALfilter*, ALenum param, int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
+void FilterTable<HighpassFilterTable>::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<HighpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
-{ getParami(filter, param, values); }
+void FilterTable<HighpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
+{ getParami(context, filter, param, values); }
 template<>
-void FilterTable<HighpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val)
+void FilterTable<HighpassFilterTable>::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<HighpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ getParamf(filter, param, vals); }
+void FilterTable<HighpassFilterTable>::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals)
+{ getParamf(context, filter, param, vals); }
 
 /* Bandpass parameter handlers */
 template<>
-void FilterTable<BandpassFilterTable>::setParami(ALfilter*, ALenum param, int)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
+void FilterTable<BandpassFilterTable>::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<BandpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
-{ setParami(filter, param, *values); }
+void FilterTable<BandpassFilterTable>::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values)
+{ setParami(context, filter, param, *values); }
 template<>
-void FilterTable<BandpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val)
+void FilterTable<BandpassFilterTable>::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<BandpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ setParamf(filter, param, *vals); }
+void FilterTable<BandpassFilterTable>::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(context, filter, param, *vals); }
 template<>
-void FilterTable<BandpassFilterTable>::getParami(const ALfilter*, ALenum param, int*)
-{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
+void FilterTable<BandpassFilterTable>::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<BandpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
-{ getParami(filter, param, values); }
+void FilterTable<BandpassFilterTable>::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values)
+{ getParami(context, filter, param, values); }
 template<>
-void FilterTable<BandpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val)
+void FilterTable<BandpassFilterTable>::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val)
 {
     switch(param)
     {
@@ -344,32 +351,35 @@ void FilterTable<BandpassFilterTable>::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<BandpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ getParamf(filter, param, vals); }
+void FilterTable<BandpassFilterTable>::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<std::mutex> filterlock{device->FilterLock};
+    auto *device = context->mALDevice.get();
+    auto filterlock = std::lock_guard{device->FilterLock};
 
     const al::span fids{filters, static_cast<ALuint>(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<std::mutex> 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<ALuint>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::mutex> 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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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);
 }

+ 15 - 9
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<typename T>
 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 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 setParami(ALCcontext*, ALfilter*, ALenum, int);
+    static void setParamiv(ALCcontext*, ALfilter*, ALenum, const int*);
+    static void setParamf(ALCcontext*, ALfilter*, ALenum, float);
+    static void setParamfv(ALCcontext*, ALfilter*, ALenum, const float*);
+
+    static void getParami(ALCcontext*, const ALfilter*, ALenum, int*);
+    static void getParamiv(ALCcontext*, const ALfilter*, ALenum, int*);
+    static void getParamf(ALCcontext*, const ALfilter*, ALenum, float*);
+    static void getParamfv(ALCcontext*, const ALfilter*, ALenum, float*);
+
+private:
+    FilterTable() = default;
+    friend T;
 };
 
 struct NullFilterTable : public FilterTable<NullFilterTable> { };

+ 90 - 55
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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<const float,6>{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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<const ALint> vals;
     switch(param)
@@ -229,11 +245,13 @@ try {
     }
 
     std::lock_guard<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::mutex> 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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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<std::mutex> 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<std::mutex> proplock{context->mPropLock};
@@ -359,10 +389,13 @@ try {
         *value3 = static_cast<ALint>(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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+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::base_exception&) {
 }
-catch(al::context_error& e) {
-    context->setError(e.errorCode(), "%s", e.what());
+catch(std::exception &e) {
+    ERR("Caught exception: {}", e.what());
 }

ファイルの差分が大きいため隠しています
+ 228 - 212
libs/openal-soft/al/source.cpp


+ 51 - 37
libs/openal-soft/al/source.h

@@ -1,6 +1,8 @@
 #ifndef AL_SOURCE_H
 #define AL_SOURCE_H
 
+#include "config.h"
+
 #include <array>
 #include <cstddef>
 #include <cstdint>
@@ -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<float>::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<EAXSPEAKERLEVELPROPERTIES, eax_max_speakers>;
     using EaxSends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
 
-    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<const Eax3Props&>(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<const GUID> 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);

+ 86 - 53
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<Resampler::Point>
 { static constexpr const ALchar *Get() noexcept { return "Nearest"; } };
 template<> struct ResamplerName<Resampler::Linear>
 { static constexpr const ALchar *Get() noexcept { return "Linear"; } };
-template<> struct ResamplerName<Resampler::Cubic>
-{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } };
+template<> struct ResamplerName<Resampler::Spline>
+{ static constexpr const ALchar *Get() noexcept { return "Cubic Spline"; } };
+template<> struct ResamplerName<Resampler::Gaussian>
+{ static constexpr const ALchar *Get() noexcept { return "4-point Gaussian"; } };
 template<> struct ResamplerName<Resampler::FastBSinc12>
 { static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } };
 template<> struct ResamplerName<Resampler::BSinc12>
@@ -92,6 +95,10 @@ template<> struct ResamplerName<Resampler::FastBSinc24>
 { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
 template<> struct ResamplerName<Resampler::BSinc24>
 { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
+template<> struct ResamplerName<Resampler::FastBSinc48>
+{ static constexpr const ALchar *Get() noexcept { return "47th order Sinc (fast)"; } };
+template<> struct ResamplerName<Resampler::BSinc48>
+{ 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<std::mutex> 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<std::mutex> 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<ALint>(Resampler::Max))
+        if(index >= 0 && index <= al::to_underlying(Resampler::Max))
             return GetResamplerName(static_cast<Resampler>(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<std::mutex> 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;

ファイルの差分が大きいため隠しています
+ 260 - 166
libs/openal-soft/alc/alc.cpp


+ 93 - 61
libs/openal-soft/alc/alconfig.cpp

@@ -34,7 +34,6 @@
 #include <array>
 #include <cctype>
 #include <cstdlib>
-#include <filesystem>
 #include <fstream>
 #include <istream>
 #include <limits>
@@ -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 <winrt/Windows.Media.Core.h> // !!This is important!!
 #include <winrt/Windows.Storage.h>
 #include <winrt/Windows.Foundation.h>
@@ -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<int>::max())
+        if(valpart.size() > size_t{std::numeric_limits<int>::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<WCHAR,CoTaskMemDeleter> 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<std::string> 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<std::string>
 {
-    if(const char *val{GetConfigValue(devName, blockName, keyName)})
+    if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty())
         return val;
     return std::nullopt;
 }
 
-std::optional<int> 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<int>
 {
-    if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return static_cast<int>(std::strtol(val, nullptr, 0));
+    if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
+        return static_cast<int>(std::stol(val, nullptr, 0));
+    }
+    catch(std::exception&) {
+        WARN("Option is not an int: {} = {}", keyName, val);
+    }
+
     return std::nullopt;
 }
 
-std::optional<unsigned int> 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<unsigned int>
 {
-    if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
+    if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try {
+        return static_cast<unsigned int>(std::stoul(val, nullptr, 0));
+    }
+    catch(std::exception&) {
+        WARN("Option is not an unsigned int: {} = {}", keyName, val);
+    }
     return std::nullopt;
 }
 
-std::optional<float> 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<float>
 {
-    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<bool> 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<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&) {
+        /* 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;
 }

+ 273 - 249
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 <array>
 #include <atomic>
 #include <cassert>
-#include <chrono>
-#include <climits>
+#include <cmath>
 #include <cstdarg>
+#include <cstddef>
 #include <cstdint>
 #include <cstdio>
 #include <cstdlib>
-#include <functional>
 #include <iterator>
 #include <limits>
 #include <memory>
-#include <new>
 #include <optional>
+#include <string>
+#include <string_view>
 #include <utility>
+#include <variant>
 
 #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<const FloatBufferLine> InSamples, float2 *AccumSamples,
-    const al::span<float,BufferLineSize> TempBuf, HrtfChannelState *ChanState, const size_t IrSize,
-    const size_t BufferSize);
+    const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
+    const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
+    const size_t IrSize, const size_t SamplesToDo);
 
 HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
 
 inline HrtfDirectMixerFunc SelectHrtfMixer()
 {
-#ifdef HAVE_NEON
+#if HAVE_NEON
     if((CPUCapFlags&CPU_CAP_NEON))
         return MixDirectHrtf_<NEONTag>;
 #endif
-#ifdef HAVE_SSE
+#if HAVE_SSE
     if((CPUCapFlags&CPU_CAP_SSE))
         return MixDirectHrtf_<SSETag>;
 #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_<PointTag,CTag>;
     case Resampler::Linear:
-#ifdef HAVE_NEON
+#if HAVE_NEON
         if((CPUCapFlags&CPU_CAP_NEON))
             return Resample_<LerpTag,NEONTag>;
 #endif
-#ifdef HAVE_SSE4_1
+#if HAVE_SSE4_1
         if((CPUCapFlags&CPU_CAP_SSE4_1))
             return Resample_<LerpTag,SSE4Tag>;
 #endif
-#ifdef HAVE_SSE2
+#if HAVE_SSE2
         if((CPUCapFlags&CPU_CAP_SSE2))
             return Resample_<LerpTag,SSE2Tag>;
 #endif
         return Resample_<LerpTag,CTag>;
-    case Resampler::Cubic:
-#ifdef HAVE_NEON
+    case Resampler::Spline:
+    case Resampler::Gaussian:
+#if HAVE_NEON
         if((CPUCapFlags&CPU_CAP_NEON))
             return Resample_<CubicTag,NEONTag>;
 #endif
-#ifdef HAVE_SSE4_1
+#if HAVE_SSE4_1
         if((CPUCapFlags&CPU_CAP_SSE4_1))
             return Resample_<CubicTag,SSE4Tag>;
 #endif
-#ifdef HAVE_SSE2
+#if HAVE_SSE2
         if((CPUCapFlags&CPU_CAP_SSE2))
             return Resample_<CubicTag,SSE2Tag>;
 #endif
-#ifdef HAVE_SSE
+#if HAVE_SSE
         if((CPUCapFlags&CPU_CAP_SSE))
             return Resample_<CubicTag,SSETag>;
 #endif
         return Resample_<CubicTag,CTag>;
     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_<BSincTag,NEONTag>;
 #endif
-#ifdef HAVE_SSE
+#if HAVE_SSE
             if((CPUCapFlags&CPU_CAP_SSE))
                 return Resample_<BSincTag,SSETag>;
 #endif
             return Resample_<BSincTag,CTag>;
         }
-        /* 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_<FastBSincTag,NEONTag>;
 #endif
-#ifdef HAVE_SSE
+#if HAVE_SSE
         if((CPUCapFlags&CPU_CAP_SSE))
             return Resample_<FastBSincTag,SSETag>;
 #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<CubicState>(al::span{gSplineFilter.mTable});
+        break;
+    case Resampler::Gaussian:
         state->emplace<CubicState>(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<BsincState>(), &gBSinc24);
         break;
+    case Resampler::FastBSinc48:
+    case Resampler::BSinc48:
+        BsincPrepare(increment, &state->emplace<BsincState>(), &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<ReverbProps>(&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<AsyncEffectReleaseEvent>(evt_vec.first.buf);
+            auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(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<size_t>(i+2_z)]};
         const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]};
@@ -698,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
         return ri0*R[last_band + static_cast<size_t>(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<float> : (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<float>(Device->Frequency);
+    const auto Frequency = static_cast<float>(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<float>/2.0f)*Spread};
+                const float a{1.0f - (al::numbers::inv_pi_v<float>/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<float>/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<float>/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<EffectSlot*,MaxSendCount> SendSlots;
+    std::array<EffectSlot*,MaxSendCount> 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<float>(voice->mFrequency) /
-        static_cast<float>(Device->Frequency) * props->Pitch;
+        static_cast<float>(Device->mSampleRate) * props->Pitch;
     if(Pitch > float{MaxPitch})
         voice->mStep = MaxPitch<<MixerFracBits;
     else
@@ -1474,13 +1495,13 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
     voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
 
     /* Calculate gains */
-    GainTriplet DryGain;
-    DryGain.Base  = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
+    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<GainTriplet,MaxSendCount> WetGain;
+    std::array<GainTriplet,MaxSendCount> 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<EffectSlot*,MaxSendCount> SendSlots{};
     std::array<float,MaxSendCount> RoomRolloff{};
-    std::bitset<MaxSendCount> 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<GainTriplet,MaxSendCount> 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<float>::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<float>::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<float>::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<float>::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<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
+    Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->mSampleRate);
     if(Pitch > float{MaxPitch})
         voice->mStep = MaxPitch<<MixerFracBits;
     else
@@ -1807,9 +1816,9 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
 {
     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<AsyncSourceStateEvent>(evt_vec.first.buf);
+    auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec[0].buf);
     evt.mId = id;
     switch(state)
     {
@@ -1929,7 +1938,7 @@ void ProcessVoiceChanges(ContextBase *ctx)
 }
 
 void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
-    const al::span<Voice*> voices)
+    const al::span<EffectSlot*> sorted_slots, const al::span<Voice*> voices)
 {
     ProcessVoiceChanges(ctx);
 
@@ -1937,9 +1946,9 @@ void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> 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<Voice*> 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;
-
-                        --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);
+                    if(next_target == split_point) UNLIKELY
+                        break;
+
+                    --next_target;
+                    auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool
+                    { return slot->Target != *next_target; };
+                    split_point = std::partition(sorted_slots.begin(), split_point, not_next);
                 }
             }
 
-            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<const FloatBufferLine> InBuffer, void *OutBuffer, cons
     ASSUME(FrameStep > 0);
     ASSUME(SamplesToDo > 0);
 
-    const auto output = al::span{static_cast<T*>(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<T*>(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<T>(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<T>(src.front());
+        std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept
         {
-            out[c] = SampleConv<T>(s);
             out += ptrdiff_t(FrameStep);
-        };
-        std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample);
-        ++c;
+            *out = SampleConv<T>(s);
+        });
     }
-    if(const size_t extra{FrameStep - c})
+}
+
+template<typename T>
+void Write(const al::span<const FloatBufferLine> InBuffer, al::span<void*> OutBuffers,
+    const size_t Offset, const size_t SamplesToDo)
+{
+    ASSUME(SamplesToDo > 0);
+
+    auto srcbuf = InBuffer.cbegin();
+    for(auto *dstbuf : OutBuffers)
     {
-        const auto silence = SampleConv<T>(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<T*>(dstbuf), Offset+SamplesToDo};
+        const auto dst = dst_.subspan(Offset);
+        std::transform(src.begin(), src.end(), dst.begin(), SampleConv<T>);
+        ++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<float*> outBuffers, const uint numSamples)
+void DeviceBase::renderSamples(const al::span<void*> outBuffers, const uint numSamples)
 {
     FPUCtl mixer_mode{};
     uint total{0};
@@ -2222,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span<float*> 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<DevFmtType_t<T>>(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<AsyncDisconnectEvent>};
         auto &disconnect = std::get<AsyncDisconnectEvent>(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<size_t>(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<AsyncEvent*>(evt_data.buf), evt);

+ 7 - 4
libs/openal-soft/alc/alu.h

@@ -2,20 +2,23 @@
 #define ALU_H
 
 #include <bitset>
+#include <cstdint>
 #include <optional>
-#include <stdint.h>
 
 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<StereoEncoding> stereomode);
+void aluInitRenderer(al::Device *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
 
 void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
 

+ 116 - 126
libs/openal-soft/alc/backends/alsa.cpp

@@ -29,7 +29,6 @@
 #include <chrono>
 #include <cstring>
 #include <exception>
-#include <functional>
 #include <memory>
 #include <mutex>
 #include <string>
@@ -38,16 +37,15 @@
 #include <utility>
 #include <vector>
 
-#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 <alsa/asoundlib.h>
@@ -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<DevMap> 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<DevMap> 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<DevMap> 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<uint>(dev));
@@ -385,42 +383,28 @@ std::vector<DevMap> 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<int>(avails)));
+            ERR("available update failed: {}", snd_strerror(static_cast<int>(avails)));
             continue;
         }
         snd_pcm_uframes_t avail{static_cast<snd_pcm_uframes_t>(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<snd_pcm_uframes_t>(commitres) != frames)
             {
-                ERR("mmap commit error: %s\n",
+                ERR("mmap commit error: {}",
                     snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(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<int>(avail)));
+            ERR("available update failed: {}", snd_strerror(static_cast<int>(avail)));
             continue;
         }
 
         if(static_cast<snd_pcm_uframes_t>(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<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
-    uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
-    uint rate{mDevice->Frequency};
+    bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)};
+    uint periodLen{static_cast<uint>(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)};
+    uint bufferLen{static_cast<uint>(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<uint>(bufferSizeInFrames);
-    mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames);
-    mDevice->Frequency = rate;
+    mDevice->mBufferSize = static_cast<uint>(bufferSizeInFrames);
+    mDevice->mUpdateSize = static_cast<uint>(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<size_t>(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<std::mutex> 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<snd_pcm_sframes_t>(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<int>(amt)));
+            ERR("read error: {}", snd_strerror(static_cast<int>(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<int>(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<int>(avail)));
+        ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast<int>(avail)));
 
         avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
         if(avail >= 0)
@@ -1152,29 +1146,29 @@ uint AlsaCapture::availableSamples()
         if(avail < 0)
         {
             const char *err{snd_strerror(static_cast<int>(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<snd_pcm_sframes_t>(avail, 0);
         avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
-        if(avail > mLastAvail) mLastAvail = avail;
+        mLastAvail = std::max(mLastAvail, avail);
         return static_cast<uint>(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<snd_pcm_sframes_t>(vec.first.len), avail)};
-        amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast<snd_pcm_uframes_t>(amt));
+        snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec[0].len), avail)};
+        amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast<snd_pcm_uframes_t>(amt));
         if(amt < 0)
         {
-            ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
+            ERR("read error: {}", snd_strerror(static_cast<int>(amt)));
 
             if(amt == -EAGAIN)
                 continue;
@@ -1189,8 +1183,8 @@ uint AlsaCapture::availableSamples()
             if(amt < 0)
             {
                 const char *err{snd_strerror(static_cast<int>(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<snd_pcm_sframes_t>(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>
 {
-    std::string outnames;
-
+    std::vector<std::string> 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;
     }

+ 5 - 5
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<std::string> 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 */

+ 41 - 23
libs/openal-soft/alc/backends/base.cpp

@@ -3,36 +3,18 @@
 
 #include "base.h"
 
-#include <algorithm>
 #include <array>
 #include <atomic>
+#include <utility>
 
-#ifdef _WIN32
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <mmreg.h>
-
-#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;

+ 33 - 14
libs/openal-soft/alc/backends/base.h

@@ -5,13 +5,14 @@
 #include <cstdarg>
 #include <cstddef>
 #include <memory>
-#include <ratio>
 #include <string>
 #include <string_view>
+#include <vector>
 
+#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<std::string> = 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<typename ...Args>
+    backend_exception(backend_error code, fmt::format_string<Args...> 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; }
 };
 

+ 171 - 100
libs/openal-soft/alc/backends/coreaudio.cpp

@@ -24,7 +24,9 @@
 
 #include <cinttypes>
 #include <cmath>
+#include <functional>
 #include <memory>
+#include <optional>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -32,13 +34,13 @@
 #include <string.h>
 #include <unistd.h>
 #include <vector>
-#include <optional>
 
 #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 <AudioUnit/AudioUnit.h>
@@ -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<AudioChannelLabel, 1> MonoChanMap { kAudioChannelLabel_Mono };
+constexpr std::array<AudioChannelLabel, 2> StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right};
+constexpr std::array<AudioChannelLabel, 4> QuadChanMap {
+        kAudioChannelLabel_Left, kAudioChannelLabel_Right,
+        kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
+};
+constexpr std::array<AudioChannelLabel, 6> X51ChanMap {
+        kAudioChannelLabel_Left, kAudioChannelLabel_Right,
+        kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
+        kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround
+};
+constexpr std::array<AudioChannelLabel, 6> X51RearChanMap {
+        kAudioChannelLabel_Left, kAudioChannelLabel_Right,
+        kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
+        kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
+};
+constexpr std::array<AudioChannelLabel, 7> X61ChanMap {
+        kAudioChannelLabel_Left, kAudioChannelLabel_Right,
+        kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen,
+        kAudioChannelLabel_CenterSurround,
+        kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft
+};
+constexpr std::array<AudioChannelLabel, 8> 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<UInt32>(code)} { }
+    explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast<UInt32>(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<DeviceEntry> &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<AudioDeviceID>(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<DeviceEntry> &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<DeviceEntry> &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<DeviceEntry> &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->mBufferSize = static_cast<uint>(mDevice->mBufferSize*streamFormat.mSampleRate/
+            mDevice->mSampleRate + 0.5);
+        mDevice->mSampleRate = static_cast<uint>(streamFormat.mSampleRate);
+    }
+
+    struct ChannelMap {
+        DevFmtChannels fmt;
+        al::span<const AudioChannelLabel> map;
+        bool is_51rear;
+    };
+
+    static constexpr std::array<ChannelMap,7> 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))
     {
-        mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
-            mDevice->Frequency + 0.5);
-        mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
+        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<char[]>(propSize);
+            auto *layout = reinterpret_cast<AudioChannelLayout*>(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<AudioChannelLayoutTag>(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;
+            }
+        }
     }
 
-    /* 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
+    /* 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<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
+    double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate};
+    auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->mBufferSize*srateScale)),
         static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
     FrameCount64 += MaxResamplerPadding;
     if(FrameCount64 > std::numeric_limits<int32_t>::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<uint>(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<uint>(rec_vec.first.len);
+    const void *src0{rec_vec[0].buf};
+    auto src0len = static_cast<uint>(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<uint>(rec_vec.second.len);
+        const void *src1{rec_vec[1].buf};
+        auto src1len = static_cast<uint>(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>
 {
-    std::string outnames;
+    std::vector<std::string> 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

+ 6 - 6
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<std::string> 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 */

+ 116 - 130
libs/openal-soft/alc/backends/dsound.cpp

@@ -37,23 +37,20 @@
 #include <cassert>
 #include <cstdio>
 #include <cstdlib>
-#include <functional>
 #include <memory.h>
-#include <mutex>
 #include <string>
 #include <thread>
 #include <vector>
 
-#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<std::vector<DevMap>*>(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<WORD>(mDevice->channelsFromFmt());
-    OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
-    OutputType.Format.nBlockAlign = static_cast<WORD>(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;
-
-    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;
+    do {
+        hr = S_OK;
+        OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
+        OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
+        OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
+        OutputType.Format.nBlockAlign = static_cast<WORD>(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;
 
-        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);
+            OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+            /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
+            OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
+            OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+            if(mDevice->FmtType == DevFmtFloat)
+                OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+            else
+                OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+            mPrimaryBuffer = nullptr;
+        }
+        else
+        {
+            if(SUCCEEDED(hr) && !mPrimaryBuffer)
+            {
+                DSBUFFERDESC DSBDescription{};
+                DSBDescription.dwSize = sizeof(DSBDescription);
+                DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
+                hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
+            }
+            if(SUCCEEDED(hr))
+                hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
         }
-        if(SUCCEEDED(hr))
-            hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
-    }
 
-    if(SUCCEEDED(hr))
-    {
-        uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+        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<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
+            std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> 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<WORD>(mDevice->bytesFromFmt() * 8);
     InputType.Format.nBlockAlign = static_cast<WORD>(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<uint>(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>
 {
-    std::string outnames;
+    std::vector<std::string> 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;
     }

+ 5 - 5
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<std::string> 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 */

+ 132 - 107
libs/openal-soft/alc/backends/jack.cpp

@@ -29,19 +29,17 @@
 #include <memory.h>
 #include <mutex>
 #include <thread>
-#include <functional>
 #include <vector>
 
-#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 <jack/jack.h>
@@ -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<typename T, typename U>
     DeviceEntry(T&& name, U&& pattern)
         : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
     { }
+    ~DeviceEntry();
+
+    DeviceEntry& operator=(const DeviceEntry&) = default;
+    DeviceEntry& operator=(DeviceEntry&&) = default;
 };
+DeviceEntry::~DeviceEntry() = default;
 
 std::vector<DeviceEntry> PlaybackList;
 
@@ -181,21 +189,21 @@ void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &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<DeviceEntry> &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<DeviceEntry> &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<DeviceEntry> &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<jack_default_audio_sample_t*,MaxOutputChannels> out;
-    size_t numchans{0};
+    auto outptrs = std::array<void*,MaxOutputChannels>{};
+    auto numchans = size_t{0};
     for(auto port : mPort)
     {
         if(!port || numchans == mDevice->RealOut.Buffer.size())
             break;
-        out[numchans++] = static_cast<float*>(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<uint>(numframes));
+        mDevice->renderSamples(dst, static_cast<uint>(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<float*>(outbuf), numframes, 0.0f); });
     }
 
     return 0;
@@ -354,43 +361,38 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
         out[numchans++] = {static_cast<float*>(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<jack_nframes_t>(data.first.len))};
-        auto firstin = al::span{reinterpret_cast<const float*>(data.first.buf), todo};
-        for(size_t c{0};c < numchans;++c)
+        const auto update_size = size_t{mDevice->mUpdateSize};
+
+        const auto outlen = size_t{numframes / update_size};
+        const auto len1 = size_t{std::min(data[0].len/update_size, outlen)};
+        const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)};
+
+        auto src = al::span{reinterpret_cast<float*>(data[0].buf), update_size*len1*numchans};
+        for(size_t i{0};i < len1;++i)
         {
-            auto in = firstin.cbegin();
-            auto deinterlace_input = [&in,c,numchans]() noexcept -> float
+            for(size_t c{0};c < numchans;++c)
             {
-                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);
+                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;
         }
-        total += todo;
 
-        todo = std::min(numframes-total, static_cast<jack_nframes_t>(data.second.len));
-        if(todo > 0)
+        src = al::span{reinterpret_cast<float*>(data[1].buf), update_size*len2*numchans};
+        for(size_t i{0};i < len2;++i)
         {
-            auto secondin = al::span{reinterpret_cast<const float*>(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;
         }
 
         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<void*>(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<uint>(std::min(data.first.len, todo));
-        const auto len2 = static_cast<uint>(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<std::mutex> dlock{mMutex};
-        mDevice->renderSamples(data.first.buf, len1, frame_step);
+        auto buffer = al::span{reinterpret_cast<float*>(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<float*>(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<std::mutex> 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>
 {
-    std::string outnames;
+    std::vector<std::string> 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:

+ 5 - 5
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<std::string> 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 */

+ 4 - 4
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<std::string>
+{ return {}; }
 
 BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
 { return BackendPtr{new LoopbackBackend{device}}; }

+ 5 - 5
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<std::string> 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 */

+ 19 - 21
libs/openal-soft/alc/backends/null.cpp

@@ -27,11 +27,8 @@
 #include <chrono>
 #include <cstdint>
 #include <cstring>
-#include <functional>
 #include <thread>
 
-#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<seconds>((now-start) * mDevice->Frequency).count()};
-        if(avail-done < mDevice->UpdateSize)
+        const auto avail = int64_t{std::chrono::duration_cast<seconds>((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<std::string>
 {
     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)

+ 5 - 5
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<std::string> 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 */

+ 35 - 34
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<int32_t>(mDevice->Frequency));
+        builder.setSampleRate(static_cast<int32_t>(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<int32_t>(mDevice->BufferSize),
+    mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(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<uint>(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<uint32_t>(mStream->getSampleRate());
+    mDevice->mSampleRate = static_cast<uint32_t>(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<uint32_t>(mStream->getFramesPerBurst()));
-    mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
+    mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u,
         static_cast<uint32_t>(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<int32_t>(mDevice->Frequency))
+        ->setSampleRate(static_cast<int32_t>(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<uint32_t>(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<std::string>
 {
     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)

+ 5 - 5
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<std::string> 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 */

+ 131 - 72
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<std::mutex> dlock{mMutex};
         auto data = mRing->getWriteVector();
-        mDevice->renderSamples(data.first.buf,
-            static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
-        if(data.second.len > 0)
-            mDevice->renderSamples(data.second.buf,
-                static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
+        mDevice->renderSamples(data[0].buf,
+            static_cast<uint>(data[0].len)*mDevice->mUpdateSize, frame_step);
+        if(data[1].len > 0)
+            mDevice->renderSamples(data[1].buf,
+                static_cast<uint>(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<std::mutex> 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<uint>(mRing->writeSpace() * update_len);
+        mDevice->mUpdateSize = update_len;
+        mDevice->mBufferSize = static_cast<uint>(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<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
+{ return static_cast<uint>(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<decltype(p##f)>(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<std::string>
 {
     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)

+ 5 - 5
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<std::string> 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 */

+ 72 - 79
libs/openal-soft/alc/backends/oss.cpp

@@ -33,7 +33,6 @@
 #include <cerrno>
 #include <cstring>
 #include <exception>
-#include <functional>
 #include <memory>
 #include <string>
 #include <string_view>
@@ -43,13 +42,12 @@
 #include <vector>
 
 #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 <sys/soundcard.h>
@@ -169,18 +167,13 @@ void ALCossListAppend(std::vector<DevMap> &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);
-    }
-
-    const DevMap &entry = list.emplace_back(std::move(newname), path);
+        newname = fmt::format("{} #{}", handle, ++count);
 
-    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<DevMap> &devlist, int type_flag)
@@ -189,13 +182,13 @@ void ALCossListPopulate(std::vector<DevMap> &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<DevMap> &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<uint>(info.fragsize) / frameSize;
-    mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
+    mDevice->mSampleRate = ossSpeed;
+    mDevice->mUpdateSize = static_cast<uint>(info.fragsize) / frameSize;
+    mDevice->mBufferSize = static_cast<uint>(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<size_t>(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>
 {
-    std::string outnames;
-
+    std::vector<std::string> 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;
     }

+ 5 - 5
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<std::string> 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 */

+ 700 - 0
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 <windows.h>
+#include <winreg.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <memory.h>
+
+#include <wtypes.h>
+#include <cguid.h>
+#include <devpropdef.h>
+#include <mmreg.h>
+#include <propsys.h>
+#include <propkey.h>
+#include <devpkey.h>
+#ifndef _WAVEFORMATEXTENSIBLE_
+#include <ks.h>
+#include <ksmedia.h>
+#endif
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <cstring>
+#include <deque>
+#include <future>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <vector>
+
+#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<typename T>
+    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<int64_t>(as<uint64_t>()); }
+template<> [[nodiscard]]
+auto ORIO64Bit::as() const -> double { return al::bit_cast<double>(as<uint64_t>()); }
+
+
+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<char,32> mName;
+};
+
+struct ORIOChannelInfo {
+    LONG mChannel;
+    LONG mIsInput;
+    LONG mIsActive;
+    LONG mGroup;
+    ORIOSampleType mSampleType;
+    std::array<char,32> mName;
+};
+
+struct ORIOBufferInfo {
+    LONG mIsInput;
+    LONG mChannelNum;
+    std::array<void*,2> mBuffers;
+};
+
+struct ORIOTime {
+    struct TimeInfo {
+        double mSpeed;
+        ORIO64Bit mSystemTime;
+        ORIO64Bit mSamplePosition;
+        double mSampleRate;
+        ULONG mFlags;
+        std::array<char,12> mReserved;
+    };
+    struct TimeCode {
+        double mSpeed;
+        ORIO64Bit mTimeCodeSamples;
+        ULONG mFlags;
+        std::array<char,64> mFuture;
+    };
+
+    std::array<LONG,4> 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<char,32> name) = 0;
+    STDMETHOD_(LONG, GetDriverVersion)() = 0;
+    STDMETHOD_(void, GetErrorMessage)(al::span<char,124> 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<DeviceEntry> gDeviceList;
+
+
+struct KeyCloser {
+    void operator()(HKEY key) { RegCloseKey(key); }
+};
+using KeyPtr = std::unique_ptr<std::remove_pointer_t<HKEY>,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<WCHAR>(maxkeylen*2 + 1);
+    for(DWORD i{0};i < numkeys;++i)
+    {
+        auto namelen = static_cast<DWORD>(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<WCHAR,48>{};
+        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<ORIOiface>{};
+        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<char,32>{};
+            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<HRESULT> mPromise;
+
+        explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
+    };
+    static inline std::deque<Msg> mMsgQueue;
+    static inline std::mutex mMsgQueueLock;
+    static inline std::condition_variable mMsgQueueCond;
+
+    auto pushMessage(MsgType type, std::string_view param={}) -> std::future<HRESULT>
+    {
+        auto promise = std::promise<HRESULT>{};
+        auto future = std::future<HRESULT>{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<HRESULT> *promise);
+};
+
+void OtherIOProxy::messageHandler(std::promise<HRESULT> *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<uint>(msg.mType),
+            static_cast<void*>(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<bool> 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<seconds>((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<HRESULT>{};
+        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::string>
+{
+    std::vector<std::string> 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;
+}

+ 21 - 0
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<std::string> final;
+
+    auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
+
+    static auto getFactory() -> BackendFactory&;
+};
+
+#endif /* BACKENDS_OTHERIO_H */

ファイルの差分が大きいため隠しています
+ 163 - 155
libs/openal-soft/alc/backends/pipewire.cpp


+ 8 - 6
libs/openal-soft/alc/backends/pipewire.h

@@ -2,24 +2,26 @@
 #define BACKENDS_PIPEWIRE_H
 
 #include <string>
+#include <vector>
 
+#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<std::string> 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 */

+ 229 - 116
libs/openal-soft/alc/backends/portaudio.cpp

@@ -20,32 +20,25 @@
 
 #include "config.h"
 
-#include "portaudio.h"
+#include "portaudio.hpp"
 
+#include <cmath>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 
-#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 <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
+#include <portaudio.h>
 
 
 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<DeviceEntry> DeviceNames;
+
+void EnumerateDevices()
+{
+    const auto devcount = Pa_GetDeviceCount();
+    if(devcount < 0)
+    {
+        ERR("Error getting device count: {}", Pa_GetErrorText(devcount));
+        return;
+    }
+
+    std::vector<DeviceEntry>(static_cast<uint>(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<uint>(std::max(info->maxOutputChannels, 0));
+            entry.mCaptureChannels = static_cast<uint>(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()};
-
-    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<double>(mDevice->Frequency);
-    params.hostApiSpecificStreamInfo = nullptr;
-
-    params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+    auto &devinfo = DeviceNames.at(static_cast<uint>(deviceid));
 
+    auto params = StreamParamsExt{};
+    params.device = deviceid;
+    params.suggestedLatency = mDevice->mBufferSize / static_cast<double>(mDevice->mSampleRate);
+    params.hostApiSpecificStreamInfo = nullptr;
+    params.channelCount = static_cast<int>(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<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
             framesPerBuffer, timeInfo, statusFlags);
     };
-    PaStream *stream{};
-    while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
-        mDevice->UpdateSize, paNoFlag, writeCallback, this)})
+    while(PaError err{Pa_OpenStream(&mStream, nullptr, &params, 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;
+}
+
+void PortPlayback::open(std::string_view name)
+{
+    if(DeviceNames.empty())
+        EnumerateDevices();
 
-    mDevice->DeviceName = name;
+    PaDeviceIndex deviceid{-1};
+    if(name.empty())
+    {
+        if(auto devidopt = ConfigValueInt({}, "port", "device"))
+            deviceid = *devidopt;
+        if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
+            deviceid = Pa_GetDefaultOutputDevice();
+        name = DeviceNames.at(static_cast<uint>(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<int>(std::distance(DeviceNames.cbegin(), iter));
+    }
+
+    createStream(deviceid);
+    mDeviceIdx = deviceid;
+
+    mDeviceName = name;
 }
 
 bool PortPlayback::reset()
 {
-    const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
-    mDevice->Frequency = static_cast<uint>(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
+    if(mStream)
     {
-        ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
-        return false;
+        auto err = Pa_CloseStream(mStream);
+        if(err != paNoError)
+            ERR("Error closing stream: {}", Pa_GetErrorText(err));
+        mStream = nullptr;
     }
 
-    if(mParams.channelCount >= 2)
-        mDevice->FmtChans = DevFmtStereo;
-    else if(mParams.channelCount == 1)
-        mDevice->FmtChans = DevFmtMono;
-    else
+    createStream(mDeviceIdx);
+
+    switch(mParams.sampleFormat)
     {
-        ERR("Unexpected channel count: %u\n", mParams.channelCount);
-        return false;
+    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<int>(mDevice->channelsFromFmt()))
+    {
+        if(mParams.channelCount >= 2)
+            mDevice->FmtChans = DevFmtStereo;
+        else if(mParams.channelCount == 1)
+            mDevice->FmtChans = DevFmtMono;
+        mDevice->mAmbiOrder = 0;
+    }
+
+    const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
+    mDevice->mSampleRate = static_cast<uint>(std::lround(streamInfo->sampleRate));
+    mDevice->mUpdateSize = mParams.updateSize;
+    mDevice->mBufferSize = mDevice->mUpdateSize * 2;
+    if(streamInfo->outputLatency > 0.0f)
+    {
+        const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
+        TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency,
+            sampleLatency);
+        mDevice->mBufferSize = static_cast<uint>(std::clamp(sampleLatency,
+            double(mDevice->mBufferSize), double{std::numeric_limits<int>::max()}));
     }
+
     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(DeviceNames.empty())
+        EnumerateDevices();
+
+    int 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()};
+    {
+        if(auto devidopt = ConfigValueInt({}, "port", "capture"))
+            deviceid = *devidopt;
+        if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
+            deviceid = Pa_GetDefaultInputDevice();
+        name = DeviceNames.at(static_cast<uint>(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<int>(std::distance(DeviceNames.cbegin(), iter));
+    }
 
-    const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
+    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<int>(mDevice->channelsFromFmt());
@@ -313,29 +390,27 @@ void PortCapture::open(std::string_view name)
         return static_cast<PortCapture*>(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::string>
 {
+    std::vector<std::string> devices;
+
+    EnumerateDevices();
+    int defaultid{-1};
     switch(type)
     {
     case BackendType::Playback:
+        defaultid = Pa_GetDefaultOutputDevice();
+        if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
+            && static_cast<uint>(*devidopt) < DeviceNames.size())
+            defaultid = *devidopt;
+
+        for(size_t i{0};i < DeviceNames.size();++i)
+        {
+            if(DeviceNames[i].mPlaybackChannels > 0)
+            {
+                if(defaultid >= 0 && static_cast<uint>(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<uint>(*devidopt) < DeviceNames.size())
+            defaultid = *devidopt;
+
+        for(size_t i{0};i < DeviceNames.size();++i)
+        {
+            if(DeviceNames[i].mCaptureChannels > 0)
+            {
+                if(defaultid >= 0 && static_cast<uint>(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)

+ 0 - 19
libs/openal-soft/alc/backends/portaudio.h

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

+ 19 - 0
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<std::string> final;
+
+    auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
+
+    static auto getFactory() -> BackendFactory&;
+};
+
+#endif /* BACKENDS_PORTAUDIO_HPP */

ファイルの差分が大きいため隠しています
+ 291 - 223
libs/openal-soft/alc/backends/pulseaudio.cpp


+ 12 - 6
libs/openal-soft/alc/backends/pulseaudio.h

@@ -1,21 +1,27 @@
 #ifndef BACKENDS_PULSEAUDIO_H
 #define BACKENDS_PULSEAUDIO_H
 
+#include <string>
+#include <vector>
+
+#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<std::string> 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 */

+ 110 - 77
libs/openal-soft/alc/backends/sdl2.cpp

@@ -28,10 +28,8 @@
 #include <string>
 #include <string_view>
 
-#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<int>(mDevice->Frequency);
+    want.freq = static_cast<int>(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<Uint16>(std::min(mDevice->UpdateSize, 8192u));
+    want.channels = static_cast<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
+        std::numeric_limits<Uint8>::max()));
+    want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
     want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
     { return static_cast<Sdl2Backend*>(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<uint>(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<int>(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<Uint8>(std::min<uint>(mDevice->channelsFromFmt(),
+        std::numeric_limits<Uint8>::max()));
+    want.samples = static_cast<Uint16>(std::min(mDevice->mUpdateSize, 8192u));
+    want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
+    { return static_cast<Sdl2Backend*>(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<uint>(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>
 {
-    std::string outnames;
+    std::vector<std::string> 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<unsigned int>(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;
 }

+ 5 - 5
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<std::string> 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 */

+ 393 - 0
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 <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <string>
+#include <string_view>
+
+#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<typename T>
+struct SdlDeleter {
+    /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */
+    void operator()(gsl::owner<T*> ptr) const { SDL_free(ptr); }
+};
+template<typename T>
+using unique_sdl_ptr = std::unique_ptr<T,SdlDeleter<T>>;
+
+
+struct DeviceEntry {
+    std::string mName;
+    SDL_AudioDeviceID mPhysDeviceID{};
+};
+
+std::vector<DeviceEntry> gPlaybackDevices;
+
+void EnumeratePlaybackDevices()
+{
+    auto numdevs = int{};
+    auto devicelist = unique_sdl_ptr<SDL_AudioDeviceID>{SDL_GetAudioPlaybackDevices(&numdevs)};
+    if(!devicelist || numdevs < 0)
+    {
+        ERR("Failed to get playback devices: {}", SDL_GetError());
+        return;
+    }
+
+    auto devids = al::span{devicelist.get(), static_cast<uint>(numdevs)};
+    auto newlist = std::vector<DeviceEntry>{};
+
+    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<std::byte> 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<unsigned int>(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<uint>(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<uint>(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<uint>(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<Sdl3Backend*>(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<int>(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<int>(std::min<uint>(mDevice->channelsFromFmt(),
+            std::numeric_limits<int>::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<uint>(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<uint>(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<uint>(have.freq);
+
+    if(update_size >= 64)
+    {
+        mDevice->mUpdateSize = static_cast<uint>(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<std::string>
+{
+    auto outnames = std::vector<std::string>{};
+
+    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;
+}

+ 19 - 0
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<std::string> final;
+
+    auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
+
+    static auto getFactory() -> BackendFactory&;
+};
+
+#endif /* BACKENDS_SDL3_H */

+ 51 - 57
libs/openal-soft/alc/backends/sndio.cpp

@@ -20,27 +20,23 @@
 
 #include "config.h"
 
-#include "sndio.h"
+#include "sndio.hpp"
 
-#include <cinttypes>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <functional>
 #include <poll.h>
 #include <system_error>
 #include <thread>
 #include <vector>
 
-#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 <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
+#include <sndio.h>
 
 
 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<std::byte> buffer{data.first.buf, data.first.len*frameSize};
+        al::span<std::byte> 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<uint>(mRing->writeSpace());
-    mDevice->UpdateSize = par.round;
+    mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false);
+    mDevice->mBufferSize = static_cast<uint>(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<std::string>
 {
     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)

+ 0 - 19
libs/openal-soft/alc/backends/sndio.h

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

+ 19 - 0
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<std::string> final;
+
+    auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
+
+    static auto getFactory() -> BackendFactory&;
+};
+
+#endif /* BACKENDS_SNDIO_HPP */

+ 26 - 26
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<std::string>
 {
     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)

+ 5 - 5
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<std::string> 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 */

ファイルの差分が大きいため隠しています
+ 634 - 475
libs/openal-soft/alc/backends/wasapi.cpp


+ 6 - 6
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<std::string> 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 */

+ 50 - 45
libs/openal-soft/alc/backends/wave.cpp

@@ -30,7 +30,6 @@
 #include <cstdio>
 #include <cstring>
 #include <exception>
-#include <functional>
 #include <system_error>
 #include <thread>
 #include <vector>
@@ -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<seconds>((now-start) *
-            mDevice->Frequency).count()};
-        if(avail-done < mDevice->UpdateSize)
+        const auto avail = int64_t{std::chrono::duration_cast<seconds>((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<ushort>(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<ushort>(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<std::string>
 {
     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)

+ 5 - 5
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<std::string> 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 */

+ 75 - 93
libs/openal-soft/alc/backends/winmm.cpp

@@ -36,15 +36,14 @@
 #include <vector>
 #include <string>
 #include <algorithm>
-#include <functional>
 
 #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<std::string> PlaybackDevices;
 std::vector<std::string> 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<UINT>(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<WORD>(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<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
-        reinterpret_cast<DWORD_PTR>(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<WORD>(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<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
+            reinterpret_cast<DWORD_PTR>(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<uint>(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<uint>(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<uint>(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<UINT>(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<WORD>(mDevice->channelsFromFmt());
     mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
     mFormat.nBlockAlign = static_cast<WORD>(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<DWORD_PTR>(&WinMMCapture::waveInProcC),
         reinterpret_cast<DWORD_PTR>(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<size_t>(mDevice->BufferSize,
+    const auto CapturedDataSize = std::max<size_t>(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>
 {
-    std::string outnames;
+    std::vector<std::string> 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;
     }

+ 5 - 5
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<std::string> 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 */

+ 123 - 105
libs/openal-soft/alc/context.cpp

@@ -6,12 +6,12 @@
 #include <algorithm>
 #include <array>
 #include <cstddef>
-#include <cstring>
 #include <functional>
-#include <limits>
+#include <iterator>
 #include <numeric>
-#include <stdexcept>
+#include <optional>
 #include <string_view>
+#include <tuple>
 #include <utility>
 
 #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<std::string_view> 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<ALCdevice> device, ContextFlagBitset flags)
+ALCcontext::ALCcontext(al::intrusive_ptr<al::Device> 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<uint>(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<uint>(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<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
+    auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)};
+    if(auto toremove = static_cast<size_t>(std::count(oldarray.begin(), oldarray.end(), this)))
     {
         using ContextArray = al::FlexArray<ContextBase*>;
-        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<typename F>
@@ -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<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
-            call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+        eax_defer<Eax4PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
+            &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
         break;
     case EAXCONTEXT_DISTANCEFACTOR:
-        eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
-            call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
+        eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
+            &EAX40CONTEXTPROPERTIES::flDistanceFactor);
         break;
     case EAXCONTEXT_AIRABSORPTIONHF:
-        eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
-            call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
+        eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
+            &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
         break;
     case EAXCONTEXT_HFREFERENCE:
-        eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
-            call, state, &EAX40CONTEXTPROPERTIES::flHFReference);
+        eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(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<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(
-            call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+        eax_defer<Eax5PrimaryFxSlotIdValidator, eax_primary_fx_slot_id_dirty_bit>(call, state,
+            &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
         break;
     case EAXCONTEXT_DISTANCEFACTOR:
-        eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(
-            call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
+        eax_defer<Eax4DistanceFactorValidator, eax_distance_factor_dirty_bit>(call, state,
+            &EAX50CONTEXTPROPERTIES::flDistanceFactor);
         break;
     case EAXCONTEXT_AIRABSORPTIONHF:
-        eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(
-            call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
+        eax_defer<Eax4AirAbsorptionHfValidator, eax_air_absorption_hf_dirty_bit>(call, state,
+            &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
         break;
     case EAXCONTEXT_HFREFERENCE:
-        eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(
-            call, state, &EAX50CONTEXTPROPERTIES::flHFReference);
+        eax_defer<Eax4HfReferenceValidator, eax_hf_reference_dirty_bit>(call, state,
+            &EAX50CONTEXTPROPERTIES::flHFReference);
         break;
     case EAXCONTEXT_MACROFXFACTOR:
-        eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(
-            call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
+        eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(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<eax_dirty_bit_count>& dst_df)
 {
-    if(mEaxDf == EaxDirtyFlags{})
+    if(mEaxDf.none())
         return;
 
-    eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
-        state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
-    eax_context_commit_property<eax_distance_factor_dirty_bit>(
-        state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor);
-    eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
-        state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
-    eax_context_commit_property<eax_hf_reference_dirty_bit>(
-        state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference);
+    eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
+        &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+    eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
+        &EAX40CONTEXTPROPERTIES::flDistanceFactor);
+    eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
+        &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF);
+    eax_context_commit_property<eax_hf_reference_dirty_bit>(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<eax_dirty_bit_count>& dst_df)
 {
-    if(mEaxDf == EaxDirtyFlags{})
+    if(mEaxDf.none())
         return;
 
-    eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(
-        state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
-    eax_context_commit_property<eax_distance_factor_dirty_bit>(
-        state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor);
-    eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(
-        state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
-    eax_context_commit_property<eax_hf_reference_dirty_bit>(
-        state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference);
-    eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(
-        state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
+    eax_context_commit_property<eax_primary_fx_slot_id_dirty_bit>(state, dst_df,
+        &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID);
+    eax_context_commit_property<eax_distance_factor_dirty_bit>(state, dst_df,
+        &EAX50CONTEXTPROPERTIES::flDistanceFactor);
+    eax_context_commit_property<eax_air_absorption_hf_dirty_bit>(state, dst_df,
+        &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF);
+    eax_context_commit_property<eax_hf_reference_dirty_bit>(state, dst_df,
+        &EAX50CONTEXTPROPERTIES::flHFReference);
+    eax_context_commit_property<eax_macro_fx_factor_dirty_bit>(state, dst_df,
+        &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
 
-    mEaxDf = EaxDirtyFlags{};
+    mEaxDf.reset();
 }
 
 void ALCcontext::eax_context_commit()
 {
-    auto dst_df = EaxDirtyFlags{};
+    auto dst_df = std::bitset<eax_dirty_bit_count>{};
 
     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();
 }
 

+ 65 - 45
libs/openal-soft/alc/context.h

@@ -1,12 +1,14 @@
 #ifndef ALC_CONTEXT_H
 #define ALC_CONTEXT_H
 
-#include <array>
+#include "config.h"
+
 #include <atomic>
+#include <bitset>
+#include <cstdint>
 #include <deque>
 #include <memory>
 #include <mutex>
-#include <stdint.h>
 #include <string>
 #include <string_view>
 #include <unordered_map>
@@ -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<ALCcontext>, ContextBase {
-    const al::intrusive_ptr<ALCdevice> mALDevice;
+namespace al {
+struct Device;
+} // namespace al
 
+struct ALCcontext final : public al::intrusive_ref<ALCcontext>, ContextBase {
+    const al::intrusive_ptr<al::Device> mALDevice;
 
     bool mPropsDirty{true};
     bool mDeferUpdates{false};
@@ -118,15 +122,15 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
     std::unique_ptr<ALeffectslot> mDefaultSlot;
 
     std::vector<std::string_view> mExtensions;
-    std::string mExtensionsString{};
+    std::string mExtensionsString;
 
     std::unordered_map<ALuint,std::string> mSourceNames;
     std::unordered_map<ALuint,std::string> mEffectSlotNames;
 
-    ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags);
+    ALCcontext(al::intrusive_ptr<al::Device> 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<ALCcontext>, 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<typename ...Args>
+    void setError(ALenum errorCode, fmt::format_string<Args...> 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<typename ...Args> [[noreturn]]
+    void throw_error(ALenum errorCode, fmt::format_string<Args...> fmt, Args&&... args)
+    { throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); }
 
     void sendDebugMessage(std::unique_lock<std::mutex> &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<eax_dirty_bit_count> 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<eax_dirty_bit_count>& 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<eax_dirty_bit_count>& dst_df);
+    void eax5_context_commit(Eax5State& state, std::bitset<eax_dirty_bit_count>& 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;
 

+ 18 - 14
libs/openal-soft/alc/device.cpp

@@ -3,6 +3,7 @@
 
 #include "device.h"
 
+#include <algorithm>
 #include <cstddef>
 #include <numeric>
 
@@ -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<uint>(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<uint>(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<uint>(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<std::string>({}, "hrtf-paths"));
     if(auto defhrtfopt = configValue<std::string>({}, "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

+ 37 - 32
libs/openal-soft/alc/device.h

@@ -1,34 +1,29 @@
 #ifndef ALC_DEVICE_H
 #define ALC_DEVICE_H
 
-#include <array>
+#include "config.h"
+
 #include <atomic>
 #include <memory>
 #include <mutex>
 #include <optional>
-#include <stdint.h>
 #include <string>
 #include <unordered_map>
-#include <utility>
+#include <string_view>
 #include <vector>
 
+#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<ALCdevice>, DeviceBase {
+struct ALCdevice { virtual ~ALCdevice() = default; };
+
+namespace al {
+
+struct Device final : public ALCdevice, al::intrusive_ref<al::Device>, 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<ALCdevice>, DeviceBase {
     std::mutex FilterLock;
     std::vector<FilterSubList> 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<ALCdevice>, DeviceBase {
     std::unordered_map<ALuint,std::string> mEffectNames;
     std::unordered_map<ALuint,std::string> 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<typename T>
-    inline std::optional<T> 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<T> = delete;
 };
 
-template<>
-inline std::optional<std::string> ALCdevice::configValue(const std::string_view block, const std::string_view key)
-{ return ConfigValueStr(DeviceName, block, key); }
-template<>
-inline std::optional<int> ALCdevice::configValue(const std::string_view block, const std::string_view key)
-{ return ConfigValueInt(DeviceName, block, key); }
-template<>
-inline std::optional<uint> ALCdevice::configValue(const std::string_view block, const std::string_view key)
-{ return ConfigValueUInt(DeviceName, block, key); }
-template<>
-inline std::optional<float> ALCdevice::configValue(const std::string_view block, const std::string_view key)
-{ return ConfigValueFloat(DeviceName, block, key); }
-template<>
-inline std::optional<bool> 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<std::string>
+{ return ConfigValueStr(mDeviceName, block, key); }
+template<> inline
+auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<int>
+{ return ConfigValueInt(mDeviceName, block, key); }
+template<> inline
+auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<uint>
+{ return ConfigValueUInt(mDeviceName, block, key); }
+template<> inline
+auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<float>
+{ return ConfigValueFloat(mDeviceName, block, key); }
+template<> inline
+auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional<bool>
+{ 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

+ 2 - 2
libs/openal-soft/alc/effects/autowah.cpp

@@ -122,7 +122,7 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
 {
     auto &props = std::get<AutowahProps>(*props_);
     const DeviceBase *device{context->mDevice};
-    const auto frequency = static_cast<float>(device->Frequency);
+    const auto frequency = static_cast<float>(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;
     }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません