浏览代码

Update OpenAL-soft to 1.23.1-bc7cb17.

Miku AuahDark 1 年之前
父节点
当前提交
73a6fc9196
共有 100 个文件被更改,包括 14982 次插入17700 次删除
  1. 152 0
      libs/openal-soft/.github/workflows/ci.yml
  2. 76 0
      libs/openal-soft/.github/workflows/makemhr.yml
  3. 1 0
      libs/openal-soft/.gitignore
  4. 384 346
      libs/openal-soft/CMakeLists.txt
  5. 112 0
      libs/openal-soft/ChangeLog
  6. 38 0
      libs/openal-soft/LICENSE-pffft
  7. 1 1
      libs/openal-soft/OpenALConfig.cmake.in
  8. 53 9
      libs/openal-soft/README.md
  9. 368 383
      libs/openal-soft/al/auxeffectslot.cpp
  10. 300 192
      libs/openal-soft/al/auxeffectslot.h
  11. 860 1090
      libs/openal-soft/al/buffer.cpp
  12. 33 36
      libs/openal-soft/al/buffer.h
  13. 620 0
      libs/openal-soft/al/debug.cpp
  14. 70 0
      libs/openal-soft/al/debug.h
  15. 127 0
      libs/openal-soft/al/direct_defs.h
  16. 566 158
      libs/openal-soft/al/eax/api.cpp
  17. 221 250
      libs/openal-soft/al/eax/api.h
  18. 218 0
      libs/openal-soft/al/eax/call.cpp
  19. 97 0
      libs/openal-soft/al/eax/call.h
  20. 457 0
      libs/openal-soft/al/eax/effect.h
  21. 32 0
      libs/openal-soft/al/eax/exception.cpp
  22. 18 0
      libs/openal-soft/al/eax/exception.h
  23. 2 2
      libs/openal-soft/al/eax/fx_slot_index.cpp
  24. 4 5
      libs/openal-soft/al/eax/fx_slot_index.h
  25. 5 14
      libs/openal-soft/al/eax/fx_slots.cpp
  26. 8 16
      libs/openal-soft/al/eax/fx_slots.h
  27. 6 0
      libs/openal-soft/al/eax/globals.h
  28. 26 0
      libs/openal-soft/al/eax/utils.cpp
  29. 21 58
      libs/openal-soft/al/eax/utils.h
  30. 7 10
      libs/openal-soft/al/eax/x_ram.h
  31. 0 324
      libs/openal-soft/al/eax_eax_call.cpp
  32. 0 117
      libs/openal-soft/al/eax_eax_call.h
  33. 0 3
      libs/openal-soft/al/eax_effect.cpp
  34. 0 44
      libs/openal-soft/al/eax_effect.h
  35. 0 63
      libs/openal-soft/al/eax_exception.cpp
  36. 0 25
      libs/openal-soft/al/eax_exception.h
  37. 0 21
      libs/openal-soft/al/eax_globals.cpp
  38. 0 22
      libs/openal-soft/al/eax_globals.h
  39. 0 36
      libs/openal-soft/al/eax_utils.cpp
  40. 0 3
      libs/openal-soft/al/eax_x_ram.cpp
  41. 350 374
      libs/openal-soft/al/effect.cpp
  42. 33 12
      libs/openal-soft/al/effect.h
  43. 138 446
      libs/openal-soft/al/effects/autowah.cpp
  44. 405 1110
      libs/openal-soft/al/effects/chorus.cpp
  45. 68 208
      libs/openal-soft/al/effects/compressor.cpp
  46. 54 30
      libs/openal-soft/al/effects/convolution.cpp
  47. 70 22
      libs/openal-soft/al/effects/dedicated.cpp
  48. 150 464
      libs/openal-soft/al/effects/distortion.cpp
  49. 150 465
      libs/openal-soft/al/effects/echo.cpp
  50. 0 63
      libs/openal-soft/al/effects/effects.cpp
  51. 46 65
      libs/openal-soft/al/effects/effects.h
  52. 231 775
      libs/openal-soft/al/effects/equalizer.cpp
  53. 144 356
      libs/openal-soft/al/effects/fshifter.cpp
  54. 148 365
      libs/openal-soft/al/effects/modulator.cpp
  55. 48 57
      libs/openal-soft/al/effects/null.cpp
  56. 93 274
      libs/openal-soft/al/effects/pshifter.cpp
  57. 867 2157
      libs/openal-soft/al/effects/reverb.cpp
  58. 224 553
      libs/openal-soft/al/effects/vmorpher.cpp
  59. 76 25
      libs/openal-soft/al/error.cpp
  60. 27 0
      libs/openal-soft/al/error.h
  61. 121 98
      libs/openal-soft/al/event.cpp
  62. 29 30
      libs/openal-soft/al/extension.cpp
  63. 400 487
      libs/openal-soft/al/filter.cpp
  64. 50 26
      libs/openal-soft/al/filter.h
  65. 210 257
      libs/openal-soft/al/listener.cpp
  66. 1 3
      libs/openal-soft/al/listener.h
  67. 415 210
      libs/openal-soft/al/source.cpp
  68. 829 584
      libs/openal-soft/al/source.h
  69. 333 672
      libs/openal-soft/al/state.cpp
  70. 109 747
      libs/openal-soft/alc/alc.cpp
  71. 207 198
      libs/openal-soft/alc/alconfig.cpp
  72. 14 7
      libs/openal-soft/alc/alconfig.h
  73. 464 272
      libs/openal-soft/alc/alu.cpp
  74. 5 5
      libs/openal-soft/alc/alu.h
  75. 180 145
      libs/openal-soft/alc/backends/alsa.cpp
  76. 73 65
      libs/openal-soft/alc/backends/base.cpp
  77. 18 35
      libs/openal-soft/alc/backends/base.h
  78. 211 129
      libs/openal-soft/alc/backends/coreaudio.cpp
  79. 2 0
      libs/openal-soft/alc/backends/coreaudio.h
  80. 91 95
      libs/openal-soft/alc/backends/dsound.cpp
  81. 92 103
      libs/openal-soft/alc/backends/jack.cpp
  82. 2 4
      libs/openal-soft/alc/backends/loopback.cpp
  83. 16 18
      libs/openal-soft/alc/backends/null.cpp
  84. 63 31
      libs/openal-soft/alc/backends/oboe.cpp
  85. 103 163
      libs/openal-soft/alc/backends/opensl.cpp
  86. 126 106
      libs/openal-soft/alc/backends/oss.cpp
  87. 416 204
      libs/openal-soft/alc/backends/pipewire.cpp
  88. 2 0
      libs/openal-soft/alc/backends/pipewire.h
  89. 60 68
      libs/openal-soft/alc/backends/portaudio.cpp
  90. 311 304
      libs/openal-soft/alc/backends/pulseaudio.cpp
  91. 2 0
      libs/openal-soft/alc/backends/pulseaudio.h
  92. 41 21
      libs/openal-soft/alc/backends/sdl2.cpp
  93. 131 122
      libs/openal-soft/alc/backends/sndio.cpp
  94. 28 33
      libs/openal-soft/alc/backends/solaris.cpp
  95. 595 189
      libs/openal-soft/alc/backends/wasapi.cpp
  96. 2 0
      libs/openal-soft/alc/backends/wasapi.h
  97. 86 84
      libs/openal-soft/alc/backends/wave.cpp
  98. 42 51
      libs/openal-soft/alc/backends/winmm.cpp
  99. 545 785
      libs/openal-soft/alc/context.cpp
  100. 352 300
      libs/openal-soft/alc/context.h

+ 152 - 0
libs/openal-soft/.github/workflows/ci.yml

@@ -0,0 +1,152 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    name: ${{matrix.config.name}}
+    runs-on: ${{matrix.config.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        config:
+        - {
+            name: "Win32-Release",
+            os: windows-latest,
+            cmake_opts: "-A Win32 \
+              -DALSOFT_TESTS=ON \
+              -DALSOFT_BUILD_ROUTER=ON \
+              -DALSOFT_REQUIRE_WINMM=ON \
+              -DALSOFT_REQUIRE_DSOUND=ON \
+              -DALSOFT_REQUIRE_WASAPI=ON",
+            build_type: "Release"
+          }
+        - {
+            name: "Win32-Debug",
+            os: windows-latest,
+            cmake_opts: "-A Win32 \
+              -DALSOFT_TESTS=ON \
+              -DALSOFT_BUILD_ROUTER=ON \
+              -DALSOFT_REQUIRE_WINMM=ON \
+              -DALSOFT_REQUIRE_DSOUND=ON \
+              -DALSOFT_REQUIRE_WASAPI=ON",
+            build_type: "Debug"
+          }
+        - {
+            name: "Win64-Release",
+            os: windows-latest,
+            cmake_opts: "-A x64 \
+              -DALSOFT_TESTS=ON \
+              -DALSOFT_BUILD_ROUTER=ON \
+              -DALSOFT_REQUIRE_WINMM=ON \
+              -DALSOFT_REQUIRE_DSOUND=ON \
+              -DALSOFT_REQUIRE_WASAPI=ON",
+            build_type: "Release"
+          }
+        - {
+            name: "Win64-Debug",
+            os: windows-latest,
+            cmake_opts: "-A x64 \
+              -DALSOFT_TESTS=ON \
+              -DALSOFT_BUILD_ROUTER=ON \
+              -DALSOFT_REQUIRE_WINMM=ON \
+              -DALSOFT_REQUIRE_DSOUND=ON \
+              -DALSOFT_REQUIRE_WASAPI=ON",
+            build_type: "Debug"
+          }
+        - {
+            name: "Win64-UWP",
+            os: windows-latest,
+            cmake_opts: "-A x64 \
+              -DALSOFT_TESTS=OFF \
+              -DCMAKE_SYSTEM_NAME=WindowsStore \
+              \"-DCMAKE_SYSTEM_VERSION=10.0\" \
+              -DALSOFT_BUILD_ROUTER=ON \
+              -DALSOFT_REQUIRE_WASAPI=ON",
+            build_type: "Release"
+          }
+        - {
+            name: "macOS-Release",
+            os: macos-latest,
+            cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \
+              -DALSOFT_TESTS=ON",
+            build_type: "Release"
+          }
+        - {
+            name: "iOS-Release",
+            os: macos-latest,
+            cmake_opts: "-GXcode \
+            -DCMAKE_SYSTEM_NAME=iOS \
+            -DALSOFT_REQUIRE_COREAUDIO=ON \
+            -DALSOFT_UTILS=OFF \
+            -DALSOFT_EXAMPLES=OFF \
+            -DALSOFT_TESTS=OFF \
+            -DALSOFT_INSTALL=OFF \
+            \"-DCMAKE_OSX_ARCHITECTURES=arm64\"",
+            build_type: "Release"
+          }
+        - {
+            name: "Linux-Release",
+            os: ubuntu-latest,
+            cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \
+              -DALSOFT_REQUIRE_ALSA=ON \
+              -DALSOFT_REQUIRE_OSS=ON \
+              -DALSOFT_REQUIRE_PORTAUDIO=ON \
+              -DALSOFT_REQUIRE_PULSEAUDIO=ON \
+              -DALSOFT_REQUIRE_JACK=ON \
+              -DALSOFT_REQUIRE_PIPEWIRE=ON \
+              -DALSOFT_TESTS=ON",
+            deps_cmdline: "sudo apt update && sudo apt-get install -qq \
+              libpulse-dev \
+              portaudio19-dev \
+              libasound2-dev \
+              libjack-dev \
+              libpipewire-0.3-dev \
+              qtbase5-dev \
+              libdbus-1-dev",
+            build_type: "Release"
+          }
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Install Dependencies
+      shell: bash
+      run: |
+        if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then
+          eval ${{matrix.config.deps_cmdline}}
+        fi
+
+    - name: Configure
+      shell: bash
+      run: |
+        cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} .
+
+    - name: Build
+      shell: bash
+      run: |
+        cmake --build build --config ${{matrix.config.build_type}}
+
+    - name: Test
+      shell: bash
+      run: |
+        cd build
+        ctest
+
+    - name: Create Archive
+      if: ${{ matrix.config.os == 'windows-latest' }}
+      shell: bash
+      run: |
+        cd build
+        mkdir archive
+        mkdir archive/router
+        cp ${{matrix.config.build_type}}/soft_oal.dll archive
+        cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router
+
+    - name: Upload Archive
+      # Upload package as an artifact of this workflow.
+      uses: actions/[email protected]
+      if: ${{ matrix.config.os == 'windows-latest' }}
+      with:
+        name: soft_oal-${{matrix.config.name}}
+        path: build/archive

+ 76 - 0
libs/openal-soft/.github/workflows/makemhr.yml

@@ -0,0 +1,76 @@
+name: makemhr
+
+on:
+  push:
+    paths:
+      - 'utils/makemhr/**'
+      - '.github/workflows/makemhr.yml'
+
+  workflow_dispatch:
+
+env:
+  BUILD_TYPE: Release
+
+jobs:
+  Win64:
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - 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: Clone libmysofa
+      run: git clone --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git libmysofa
+
+    - name: Add MSBuild to PATH
+      uses: microsoft/[email protected]
+
+    - name: Restore libmysofa NuGet packages
+      working-directory: ${{github.workspace}}/libmysofa
+      run: nuget restore ${{github.workspace}}/libmysofa/windows/libmysofa.sln
+
+    - name: Build libmysofa
+      working-directory: ${{github.workspace}}/libmysofa
+      run: msbuild /m /p:Configuration=${{env.BUILD_TYPE}} ${{github.workspace}}/libmysofa/windows/libmysofa.sln
+
+    - name: Configure CMake
+      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -D "MYSOFA_LIBRARY=${{github.workspace}}/libmysofa/windows/bin/x64/Release/mysofa.lib" -D "MYSOFA_INCLUDE_DIR=${{github.workspace}}/libmysofa/src/hrtf" -D "ZLIB_LIBRARY=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/lib/zlib.lib" -D "ZLIB_INCLUDE_DIR=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/include"
+
+    - name: Build
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+    - name: Make Artifacts folder
+      run: |
+        mkdir "Artifacts"
+        mkdir "Release"
+
+    - name: Collect artifacts
+      run: |
+        copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe"
+        copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll"
+
+    - name: Upload makemhr artifact
+      uses: actions/[email protected]
+      with:
+        name: makemhr
+        path: "Artifacts/"
+
+    - name: Compress artifacts
+      uses: papeloto/action-zip@v1
+      with:
+        files: Artifacts/
+        dest: "Release/makemhr.zip"
+
+    - name: GitHub pre-release
+      uses: "marvinpinto/action-automatic-releases@latest"
+      with:
+        repo_token: "${{secrets.GITHUB_TOKEN}}"
+        automatic_release_tag: "makemhr"
+        prerelease: true
+        title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}"
+        files: "Release/makemhr.zip"

+ 1 - 0
libs/openal-soft/.gitignore

@@ -1,6 +1,7 @@
 build*/
 winbuild
 win64build
+.vs/
 
 ## kdevelop
 *.kdev4

文件差异内容过多而无法显示
+ 384 - 346
libs/openal-soft/CMakeLists.txt


+ 112 - 0
libs/openal-soft/ChangeLog

@@ -1,3 +1,115 @@
+openal-soft-1.23.1:
+
+    Implemented the AL_SOFT_UHJ_ex extension.
+
+    Implemented the AL_SOFT_buffer_length_query extension.
+
+    Implemented the AL_SOFT_source_start_delay extension.
+
+    Implemented the AL_EXT_STATIC_BUFFER extension.
+
+    Fixed compiling with certain older versions of GCC.
+
+    Fixed compiling as a submodule.
+
+    Fixed compiling with newer versions of Oboe.
+
+    Improved EAX effect version switching.
+
+    Improved the quality of the reverb modulator.
+
+    Improved performance of the cubic resampler.
+
+    Added a compatibility option to restore AL_SOFT_buffer_sub_data. The option
+    disables AL_EXT_SOURCE_RADIUS due to incompatibility.
+
+    Reduced CPU usage when EAX is initialized and FXSlot0 or FXSlot1 are not
+    used.
+
+    Reduced memory usage for ADPCM buffer formats. They're no longer converted
+    to 16-bit samples on load.
+
+openal-soft-1.23.0:
+
+    Fixed CoreAudio capture support.
+
+    Fixed handling per-version EAX properties.
+
+    Fixed interpolating changes to the Super Stereo width source property.
+
+    Fixed detection of the update and buffer size from PipeWire.
+
+    Fixed resuming playback devices with OpenSL.
+
+    Fixed support for certain OpenAL implementations with the router.
+
+    Improved reverb environment transitions.
+
+    Improved performance of convolution reverb.
+
+    Improved quality and performance of the pitch shifter effect slightly.
+
+    Improved sub-sample precision for resampled sources.
+
+    Improved blending spatialized multi-channel sources that use the source
+    radius property.
+
+    Improved mixing 2D ambisonic sources for higher-order 3D ambisonic mixing.
+
+    Improved quadraphonic and 7.1 surround sound output slightly.
+
+    Added config options for UHJ encoding/decoding quality. Including Super
+    Stereo processing.
+
+    Added a config option for specifying the speaker distance.
+
+    Added a compatibility config option for specifying the NFC distance
+    scaling.
+
+    Added a config option for mixing on PipeWire's non-real-time thread.
+
+    Added support for virtual source nodes with PipeWire capture.
+
+    Added the ability for the WASAPI backend to use different playback rates.
+
+    Added support for SOFA files that define per-response delays in makemhr.
+
+    Changed the default fallback playback sample rate to 48khz. This doesn't
+    affect most backends, which can detect a default rate from the system.
+
+    Changed the default resampler to cubic.
+
+    Changed the default HRTF size from 32 to 64 points.
+
+openal-soft-1.22.2:
+
+    Fixed PipeWire version check.
+
+    Fixed building with PipeWire versions before 0.3.33.
+
+openal-soft-1.22.1:
+
+    Fixed CoreAudio capture.
+
+    Fixed air absorption strength.
+
+    Fixed handling 5.1 devices on Windows that use Rear channels instead of
+    Side channels.
+
+    Fixed some compilation issues on MinGW.
+
+    Fixed ALSA not being used on some systems without PipeWire and PulseAudio.
+
+    Fixed OpenSL capturing noise.
+
+    Fixed Oboe capture failing with some buffer sizes.
+
+    Added checks for the runtime PipeWire version. The same or newer version
+    than is used for building will be needed at runtime for the backend to
+    work.
+
+    Separated 3D7.1 into its own speaker configuration.
+
 openal-soft-1.22.0:
 
     Implemented the ALC_SOFT_reopen_device extension. This allows for moving

+ 38 - 0
libs/openal-soft/LICENSE-pffft

@@ -0,0 +1,38 @@
+A modified PFFFT is included, with the following license.
+
+Copyright (c) 2023  Christopher Robinson
+
+Copyright (c) 2013  Julien Pommier ( [email protected] )
+
+Copyright (c) 2004 the University Corporation for Atmospheric
+Research ("UCAR"). All rights reserved. Developed by NCAR's
+Computational and Information Systems Laboratory, UCAR,
+www.cisl.ucar.edu.
+
+Redistribution and use of the Software in source and binary forms,
+with or without modification, is permitted provided that the
+following conditions are met:
+
+- Neither the names of NCAR's Computational and Information Systems
+Laboratory, the University Corporation for Atmospheric Research,
+nor the names of its sponsors or contributors may be used to
+endorse or promote products derived from this Software without
+specific prior written permission.
+
+- Redistributions of source code must retain the above copyright
+notices, this list of conditions, and the disclaimer below.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions, and the disclaimer below in the
+documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.

+ 1 - 1
libs/openal-soft/OpenALConfig.cmake.in

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.1...3.18)
 
 include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake")
 

+ 53 - 9
libs/openal-soft/README.md

@@ -1,12 +1,12 @@
-OpenAL soft
+OpenAL Soft
 ===========
 
-`master` branch CI status :  [![Build Status](https://travis-ci.org/kcat/openal-soft.svg?branch=master)](https://travis-ci.org/kcat/openal-soft) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)
+`master` branch CI status : [![GitHub Actions Status](https://github.com/kcat/openal-soft/actions/workflows/ci.yml/badge.svg)](https://github.com/kcat/openal-soft/actions) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)
 
 OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct).
 OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture.
 
-More information is available on the [official website](http://openal-soft.org/)
+More information is available on the [official website](http://openal-soft.org/).
 
 Source Install
 -------------
@@ -17,19 +17,36 @@ directory, and run:
 cmake ..
 ```
 
-Assuming configuration went well, you can then build it, typically using GNU
-Make (KDevelop, MSVC, and others are possible depending on your system setup
-and CMake configuration).
+Alternatively, you can use any available CMake front-end, like cmake-gui,
+ccmake, or your preferred IDE's CMake project parser.
+
+Assuming configuration went well, you can then build it. The command
+`cmake --build .` will instruct CMake to build the project with the toolchain
+chosen during configuration (often GNU Make or NMake, although others are
+possible).
 
 Please Note: Double check that the appropriate backends were detected. Often,
 complaints of no sound, crashing, and missing devices can be solved by making
 sure the correct backends are being used. CMake's output will identify which
 backends were enabled.
 
-For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio
-were detected (if your target system uses them). For Windows, make sure
-DirectSound was detected.
+For most systems, you will likely want to make sure PipeWire, PulseAudio, and
+ALSA were detected (if your target system uses them). For Windows, make sure
+WASAPI was detected.
+
+
+Building openal-soft - Using vcpkg
+----------------------------------
 
+You can download and install openal-soft using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
+
+    git clone https://github.com/Microsoft/vcpkg.git
+    cd vcpkg
+    ./bootstrap-vcpkg.sh
+    ./vcpkg integrate install
+    ./vcpkg install openal-soft
+
+The openal-soft port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
 
 Utilities
 ---------
@@ -47,6 +64,33 @@ as application-agnostic behavior of the library. See alsoftrc.sample for
 available settings.
 
 
+Language Bindings
+-----------------
+
+As a C API, OpenAL Soft can be used directly by any language that can use
+functions with C linkage. For languages that can't directly use C-style
+headers, bindings may be developed to allow code written in that language to
+call into the library. Some bindings for some languages are listed here.
+
+C# Bindings:
+* [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL
+API, including some extensions. It also includes utility libraries for math and
+linear algebra, which can be useful for 3D calculations.
+
+Java Bindings:
+* [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.
+
+Python Bindings:
+* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play
+wave files and, with PyOgg, also Vorbis, Opus, and FLAC.
+
+Other bindings for these and other languages also exist. This list will grow as
+more bindings are found.
+
+
 Acknowledgements
 ----------------
 

文件差异内容过多而无法显示
+ 368 - 383
libs/openal-soft/al/auxeffectslot.cpp


+ 300 - 192
libs/openal-soft/al/auxeffectslot.h

@@ -1,33 +1,41 @@
 #ifndef AL_AUXEFFECTSLOT_H
 #define AL_AUXEFFECTSLOT_H
 
+#include <array>
 #include <atomic>
-#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <utility>
 
 #include "AL/al.h"
 #include "AL/alc.h"
-#include "AL/efx.h"
 
-#include "alc/device.h"
-#include "alc/effects/base.h"
 #include "almalloc.h"
-#include "atomic.h"
+#include "alnumeric.h"
+#include "core/effects/base.h"
 #include "core/effectslot.h"
 #include "intrusive_ptr.h"
-#include "vector.h"
 
 #ifdef ALSOFT_EAX
 #include <memory>
-
-#include "eax_eax_call.h"
-#include "eax_effect.h"
-#include "eax_fx_slot_index.h"
+#include "eax/api.h"
+#include "eax/call.h"
+#include "eax/effect.h"
+#include "eax/exception.h"
+#include "eax/fx_slot_index.h"
+#include "eax/utils.h"
 #endif // ALSOFT_EAX
 
 struct ALbuffer;
-struct ALeffect;
-struct WetBuffer;
 
+#ifdef ALSOFT_EAX
+class EaxFxSlotException : public EaxException {
+public:
+	explicit EaxFxSlotException(const char* message)
+		: EaxException{"EAX_FX_SLOT", message}
+	{}
+};
+#endif // ALSOFT_EAX
 
 enum class SlotState : ALenum {
     Initial = AL_INITIAL,
@@ -36,242 +44,342 @@ enum class SlotState : ALenum {
 };
 
 struct ALeffectslot {
+    ALuint EffectId{};
     float Gain{1.0f};
     bool  AuxSendAuto{true};
     ALeffectslot *Target{nullptr};
     ALbuffer *Buffer{nullptr};
 
-    struct {
+    struct EffectData {
         EffectSlotType Type{EffectSlotType::None};
         EffectProps Props{};
 
         al::intrusive_ptr<EffectState> State;
-    } Effect;
+    };
+    EffectData Effect;
 
     bool mPropsDirty{true};
 
     SlotState mState{SlotState::Initial};
 
-    RefCount ref{0u};
+    std::atomic<ALuint> ref{0u};
 
-    EffectSlot mSlot;
+    EffectSlot *mSlot{nullptr};
 
     /* Self ID */
     ALuint id{};
 
-    ALeffectslot();
+    ALeffectslot(ALCcontext *context);
     ALeffectslot(const ALeffectslot&) = delete;
     ALeffectslot& operator=(const ALeffectslot&) = delete;
     ~ALeffectslot();
 
-    ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
-    void updateProps(ALCcontext *context);
+    ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps,
+        ALCcontext *context);
+    void updateProps(ALCcontext *context) const;
 
-    /* This can be new'd for the context's default effect slot. */
-    DEF_NEWDEL(ALeffectslot)
+    static void SetName(ALCcontext *context, ALuint id, std::string_view name);
 
 
 #ifdef ALSOFT_EAX
 public:
-    void eax_initialize(
-        ALCcontext& al_context,
-        EaxFxSlotIndexValue index);
+    void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index);
 
-    const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept;
+    [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; }
+    [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES&
+    { return eax_; }
 
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool
+    { return call.is_get() ? eax_get(call) : eax_set(call); }
 
-    // [[nodiscard]]
-    bool eax_dispatch(const EaxEaxCall& eax_call)
-    { return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); }
-
-
-    void eax_unlock_legacy() noexcept;
-
-    void eax_commit() { eax_apply_deferred(); }
+    void eax_commit();
 
 private:
-    ALCcontext* eax_al_context_{};
+    static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0;
+    static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1;
+    static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2;
+    static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3;
+    static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4;
+    static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5;
+
+    using Exception = EaxFxSlotException;
+
+    using Eax4Props = EAX40FXSLOTPROPERTIES;
+
+    struct Eax4State {
+        Eax4Props i; // Immediate.
+    };
+
+    using Eax5Props = EAX50FXSLOTPROPERTIES;
+
+    struct Eax5State {
+        Eax5Props i; // Immediate.
+    };
+
+    struct EaxRangeValidator {
+        template<typename TValue>
+        void operator()(
+            const char* name,
+            const TValue& value,
+            const TValue& min_value,
+            const TValue& max_value) const
+        {
+            eax_validate_range<Exception>(name, value, min_value, max_value);
+        }
+    };
+
+    struct Eax4GuidLoadEffectValidator {
+        void operator()(const GUID& guidLoadEffect) const
+        {
+            if (guidLoadEffect != EAX_NULL_GUID &&
+                guidLoadEffect != EAX_REVERB_EFFECT &&
+                guidLoadEffect != EAX_AGCCOMPRESSOR_EFFECT &&
+                guidLoadEffect != EAX_AUTOWAH_EFFECT &&
+                guidLoadEffect != EAX_CHORUS_EFFECT &&
+                guidLoadEffect != EAX_DISTORTION_EFFECT &&
+                guidLoadEffect != EAX_ECHO_EFFECT &&
+                guidLoadEffect != EAX_EQUALIZER_EFFECT &&
+                guidLoadEffect != EAX_FLANGER_EFFECT &&
+                guidLoadEffect != EAX_FREQUENCYSHIFTER_EFFECT &&
+                guidLoadEffect != EAX_VOCALMORPHER_EFFECT &&
+                guidLoadEffect != EAX_PITCHSHIFTER_EFFECT &&
+                guidLoadEffect != EAX_RINGMODULATOR_EFFECT)
+            {
+                eax_fail_unknown_effect_id();
+            }
+        }
+    };
+
+    struct Eax4VolumeValidator {
+        void operator()(long lVolume) const
+        {
+            EaxRangeValidator{}(
+                "Volume",
+                lVolume,
+                EAXFXSLOT_MINVOLUME,
+                EAXFXSLOT_MAXVOLUME);
+        }
+    };
+
+    struct Eax4LockValidator {
+        void operator()(long lLock) const
+        {
+            EaxRangeValidator{}(
+                "Lock",
+                lLock,
+                EAXFXSLOT_MINLOCK,
+                EAXFXSLOT_MAXLOCK);
+        }
+    };
+
+    struct Eax4FlagsValidator {
+        void operator()(unsigned long ulFlags) const
+        {
+            EaxRangeValidator{}(
+                "Flags",
+                ulFlags,
+                0UL,
+                ~EAX40FXSLOTFLAGS_RESERVED);
+        }
+    };
+
+    struct Eax4AllValidator {
+        void operator()(const EAX40FXSLOTPROPERTIES& all) const
+        {
+            Eax4GuidLoadEffectValidator{}(all.guidLoadEffect);
+            Eax4VolumeValidator{}(all.lVolume);
+            Eax4LockValidator{}(all.lLock);
+            Eax4FlagsValidator{}(all.ulFlags);
+        }
+    };
+
+    struct Eax5OcclusionValidator {
+        void operator()(long lOcclusion) const
+        {
+            EaxRangeValidator{}(
+                "Occlusion",
+                lOcclusion,
+                EAXFXSLOT_MINOCCLUSION,
+                EAXFXSLOT_MAXOCCLUSION);
+        }
+    };
+
+    struct Eax5OcclusionLfRatioValidator {
+        void operator()(float flOcclusionLFRatio) const
+        {
+            EaxRangeValidator{}(
+                "Occlusion LF Ratio",
+                flOcclusionLFRatio,
+                EAXFXSLOT_MINOCCLUSIONLFRATIO,
+                EAXFXSLOT_MAXOCCLUSIONLFRATIO);
+        }
+    };
+
+    struct 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));
+            Eax5OcclusionValidator{}(all.lOcclusion);
+            Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio);
+        }
+    };
 
+    ALCcontext* eax_al_context_{};
     EaxFxSlotIndexValue eax_fx_slot_index_{};
-
-    EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{};
-
+    int eax_version_{}; // Current EAX version.
+    EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version.
     EaxEffectUPtr eax_effect_{};
-    bool eax_is_locked_{};
-
-
-    [[noreturn]]
-    static void eax_fail(
-        const char* message);
-
-
-    GUID eax_get_eax_default_effect_guid() const noexcept;
-    long eax_get_eax_default_lock() const noexcept;
-
-    void eax_set_eax_fx_slot_defaults();
-
-    void eax_initialize_eax();
-
-    void eax_initialize_lock();
-
-
-    void eax_initialize_effects();
-
-
-    void eax_get_fx_slot_all(
-        const EaxEaxCall& eax_call) const;
-
-    void eax_get_fx_slot(
-        const EaxEaxCall& eax_call) const;
-
-    // [[nodiscard]]
-    bool eax_get(
-        const EaxEaxCall& eax_call);
-
-
-    void eax_set_fx_slot_effect(
-        ALenum effect_type);
-
-    void eax_set_fx_slot_effect();
-
-
-    void eax_set_efx_effect_slot_gain();
-
-    void eax_set_fx_slot_volume();
-
-
-    void eax_set_effect_slot_send_auto();
-
-    void eax_set_fx_slot_flags();
-
-
-    void eax_ensure_is_unlocked() const;
-
-
-    void eax_validate_fx_slot_effect(
-        const GUID& eax_effect_id);
-
-    void eax_validate_fx_slot_volume(
-        long eax_volume);
-
-    void eax_validate_fx_slot_lock(
-        long eax_lock);
-
-    void eax_validate_fx_slot_flags(
-        unsigned long eax_flags,
-        int eax_version);
-
-    void eax_validate_fx_slot_occlusion(
-        long eax_occlusion);
-
-    void eax_validate_fx_slot_occlusion_lf_ratio(
-        float eax_occlusion_lf_ratio);
-
-    void eax_validate_fx_slot_all(
-        const EAX40FXSLOTPROPERTIES& fx_slot,
-        int eax_version);
-
-    void eax_validate_fx_slot_all(
-        const EAX50FXSLOTPROPERTIES& fx_slot,
-        int eax_version);
-
-
-    void eax_set_fx_slot_effect(
-        const GUID& eax_effect_id);
-
-    void eax_set_fx_slot_volume(
-        long eax_volume);
-
-    void eax_set_fx_slot_lock(
-        long eax_lock);
-
-    void eax_set_fx_slot_flags(
-        unsigned long eax_flags);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_occlusion(
-        long eax_occlusion);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_occlusion_lf_ratio(
-        float eax_occlusion_lf_ratio);
-
-    void eax_set_fx_slot_all(
-        const EAX40FXSLOTPROPERTIES& eax_fx_slot);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_all(
-        const EAX50FXSLOTPROPERTIES& eax_fx_slot);
-
-
-    void eax_set_fx_slot_effect(
-        const EaxEaxCall& eax_call);
-
-    void eax_set_fx_slot_volume(
-        const EaxEaxCall& eax_call);
-
-    void eax_set_fx_slot_lock(
-        const EaxEaxCall& eax_call);
-
-    void eax_set_fx_slot_flags(
-        const EaxEaxCall& eax_call);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_occlusion(
-        const EaxEaxCall& eax_call);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_occlusion_lf_ratio(
-        const EaxEaxCall& eax_call);
-
-    // [[nodiscard]]
-    bool eax_set_fx_slot_all(
-        const EaxEaxCall& eax_call);
-
-    bool eax_set_fx_slot(
-        const EaxEaxCall& eax_call);
-
-    void eax_apply_deferred();
-
-    // [[nodiscard]]
-    bool eax_set(
-        const EaxEaxCall& eax_call);
-
-
-    void eax_dispatch_effect(
-        const EaxEaxCall& eax_call);
-
+    Eax5State eax123_{}; // EAX1/EAX2/EAX3 state.
+    Eax4State eax4_{}; // EAX4 state.
+    Eax5State eax5_{}; // EAX5 state.
+    Eax5Props eax_{}; // Current EAX state.
+
+    [[noreturn]] static void eax_fail(const char* message);
+    [[noreturn]] static void eax_fail_unknown_effect_id();
+    [[noreturn]] static void eax_fail_unknown_property_id();
+    [[noreturn]] static void eax_fail_unknown_version();
+
+    // Gets a new value from EAX call,
+    // validates it,
+    // sets a dirty flag only if the new value differs form the old one,
+    // and assigns the new value.
+    template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
+    static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags)
+    {
+        const auto& src = call.get_value<Exception, const TProperties>();
+        TValidator{}(src);
+        dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{});
+        dst = src;
+    }
+
+    // Gets a new value from EAX call,
+    // validates it,
+    // sets a dirty flag without comparing the values,
+    // and assigns the new value.
+    template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
+    static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst,
+        EaxDirtyFlags& dirty_flags)
+    {
+        const auto& src = call.get_value<Exception, const TProperties>();
+        TValidator{}(src);
+        dirty_flags |= TDirtyBit;
+        dst = src;
+    }
+
+    [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool
+    { return eax_fx_slot_index_ < 2; }
+
+    void eax4_fx_slot_ensure_unlocked() const;
+
+    [[nodiscard]] static auto eax_get_efx_effect_type(const GUID& guid) -> ALenum;
+    [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&;
+    [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long;
+
+    void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept;
+    void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept;
+    void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept;
+    void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept;
+    void eax_fx_slot_set_current_defaults();
+    void eax_fx_slot_set_defaults();
+
+    static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props);
+    static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props);
+    void eax_fx_slot_get(const EaxCall& call) const;
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    bool eax_get(const EaxCall& call);
+
+    void eax_fx_slot_load_effect(int version, ALenum altype);
+    void eax_fx_slot_set_volume();
+    void eax_fx_slot_set_environment_flag();
+    void eax_fx_slot_set_flags();
+
+    void eax4_fx_slot_set_all(const EaxCall& call);
+    void eax5_fx_slot_set_all(const EaxCall& call);
+
+    [[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool;
+
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    bool eax4_fx_slot_set(const EaxCall& call);
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    bool eax5_fx_slot_set(const EaxCall& call);
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    bool eax_fx_slot_set(const EaxCall& call);
+    // Returns `true` if all sources should be updated, or `false` otherwise.
+    bool eax_set(const EaxCall& call);
+
+    template<
+        EaxDirtyFlags TDirtyBit,
+        typename TMemberResult,
+        typename TProps,
+        typename TState>
+    void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df,
+        TMemberResult TProps::*member) noexcept
+    {
+        auto& src_i = state.i;
+        auto& dst_i = eax_;
+
+        if((eax_df_ & TDirtyBit) != EaxDirtyFlags{})
+        {
+            dst_df |= TDirtyBit;
+            dst_i.*member = src_i.*member;
+        }
+    }
+
+    void eax4_fx_slot_commit(EaxDirtyFlags& dst_df);
+    void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df);
 
     // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
-    void eax_set_effect_slot_effect(EaxEffect &effect);
+    void eax_set_efx_slot_effect(EaxEffect &effect);
 
     // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)`
-    void eax_set_effect_slot_send_auto(bool is_send_auto);
+    void eax_set_efx_slot_send_auto(bool is_send_auto);
 
     // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)`
-    void eax_set_effect_slot_gain(ALfloat gain);
+    void eax_set_efx_slot_gain(ALfloat gain);
 
 public:
     class EaxDeleter {
     public:
         void operator()(ALeffectslot *effect_slot);
-    }; // EaxAlEffectSlotDeleter
+    };
 #endif // ALSOFT_EAX
 };
 
 void UpdateAllEffectSlotProps(ALCcontext *context);
 
 #ifdef ALSOFT_EAX
-
 using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, ALeffectslot::EaxDeleter>;
 
+EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context);
+void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot);
+#endif // ALSOFT_EAX
 
-EaxAlEffectSlotUPtr eax_create_al_effect_slot(
-    ALCcontext& context);
+struct EffectSlotSubList {
+    uint64_t FreeMask{~0_u64};
+    gsl::owner<std::array<ALeffectslot,64>*> EffectSlots{nullptr};
 
-void eax_delete_al_effect_slot(
-    ALCcontext& context,
-    ALeffectslot& effect_slot);
-#endif // ALSOFT_EAX
+    EffectSlotSubList() noexcept = default;
+    EffectSlotSubList(const EffectSlotSubList&) = delete;
+    EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
+      : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
+    { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
+    ~EffectSlotSubList();
+
+    EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
+    EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
+};
 
 #endif

文件差异内容过多而无法显示
+ 860 - 1090
libs/openal-soft/al/buffer.cpp


+ 33 - 36
libs/openal-soft/al/buffer.h

@@ -1,57 +1,37 @@
 #ifndef AL_BUFFER_H
 #define AL_BUFFER_H
 
+#include <array>
 #include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <utility>
 
 #include "AL/al.h"
+#include "AL/alc.h"
 
-#include "albyte.h"
 #include "alc/inprogext.h"
 #include "almalloc.h"
-#include "atomic.h"
+#include "alnumeric.h"
 #include "core/buffer_storage.h"
 #include "vector.h"
 
 #ifdef ALSOFT_EAX
-#include "eax_x_ram.h"
-#endif // ALSOFT_EAX
-
-/* User formats */
-enum UserFmtType : unsigned char {
-    UserFmtUByte = FmtUByte,
-    UserFmtShort = FmtShort,
-    UserFmtFloat = FmtFloat,
-    UserFmtMulaw = FmtMulaw,
-    UserFmtAlaw = FmtAlaw,
-    UserFmtDouble = FmtDouble,
-
-    UserFmtIMA4 = 128,
-    UserFmtMSADPCM,
-};
-enum UserFmtChannels : unsigned char {
-    UserFmtMono = FmtMono,
-    UserFmtStereo = FmtStereo,
-    UserFmtRear = FmtRear,
-    UserFmtQuad = FmtQuad,
-    UserFmtX51 = FmtX51,
-    UserFmtX61 = FmtX61,
-    UserFmtX71 = FmtX71,
-    UserFmtBFormat2D = FmtBFormat2D,
-    UserFmtBFormat3D = FmtBFormat3D,
-    UserFmtUHJ2 = FmtUHJ2,
-    UserFmtUHJ3 = FmtUHJ3,
-    UserFmtUHJ4 = FmtUHJ4,
+enum class EaxStorage : uint8_t {
+    Automatic,
+    Accessible,
+    Hardware
 };
+#endif // ALSOFT_EAX
 
 
 struct ALbuffer : public BufferStorage {
     ALbitfieldSOFT Access{0u};
 
-    al::vector<al::byte,16> mData;
+    al::vector<std::byte,16> mDataStorage;
 
-    UserFmtType OriginalType{UserFmtShort};
     ALuint OriginalSize{0};
-    ALuint OriginalAlign{0};
 
     ALuint UnpackAlign{0};
     ALuint PackAlign{0};
@@ -65,17 +45,34 @@ struct ALbuffer : public BufferStorage {
     ALuint mLoopEnd{0u};
 
     /* Number of times buffer was attached to a source (deletion can only occur when 0) */
-    RefCount ref{0u};
+    std::atomic<ALuint> ref{0u};
 
     /* Self ID */
     ALuint id{0};
 
-    DISABLE_ALLOC()
+    static void SetName(ALCcontext *context, ALuint id, std::string_view name);
+
+    DISABLE_ALLOC
 
 #ifdef ALSOFT_EAX
-    ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC};
+    EaxStorage eax_x_ram_mode{EaxStorage::Automatic};
     bool eax_x_ram_is_hardware{};
 #endif // ALSOFT_EAX
 };
 
+struct BufferSubList {
+    uint64_t FreeMask{~0_u64};
+    gsl::owner<std::array<ALbuffer,64>*> Buffers{nullptr};
+
+    BufferSubList() noexcept = default;
+    BufferSubList(const BufferSubList&) = delete;
+    BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
+    { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
+    ~BufferSubList();
+
+    BufferSubList& operator=(const BufferSubList&) = delete;
+    BufferSubList& operator=(BufferSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
+};
+
 #endif

+ 620 - 0
libs/openal-soft/al/debug.cpp

@@ -0,0 +1,620 @@
+#include "config.h"
+
+#include "debug.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cstring>
+#include <deque>
+#include <mutex>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#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/logging.h"
+#include "core/voice.h"
+#include "direct_defs.h"
+#include "effect.h"
+#include "error.h"
+#include "filter.h"
+#include "intrusive_ptr.h"
+#include "opthelpers.h"
+#include "source.h"
+
+
+/* Declared here to prevent compilers from thinking it should be inlined, which
+ * GCC warns about increasing code size.
+ */
+DebugGroup::~DebugGroup() = default;
+
+namespace {
+
+static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits");
+
+template<typename T, T ...Vals>
+constexpr auto make_array_sequence(std::integer_sequence<T, Vals...>)
+{ return std::array<T,sizeof...(Vals)>{Vals...}; }
+
+template<typename T, size_t N>
+constexpr auto make_array_sequence()
+{ return make_array_sequence(std::make_integer_sequence<T,N>{}); }
+
+
+constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional<DebugSource>
+{
+    switch(source)
+    {
+    case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API;
+    case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System;
+    case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty;
+    case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application;
+    case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other;
+    }
+    return std::nullopt;
+}
+
+constexpr auto GetDebugType(ALenum type) noexcept -> std::optional<DebugType>
+{
+    switch(type)
+    {
+    case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error;
+    case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior;
+    case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior;
+    case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability;
+    case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance;
+    case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker;
+    case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup;
+    case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup;
+    case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other;
+    }
+    return std::nullopt;
+}
+
+constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional<DebugSeverity>
+{
+    switch(severity)
+    {
+    case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High;
+    case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium;
+    case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low;
+    case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification;
+    }
+    return std::nullopt;
+}
+
+
+constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum
+{
+    switch(source)
+    {
+    case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT;
+    case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT;
+    case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT;
+    case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT;
+    case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT;
+    }
+    throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))};
+}
+
+constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum
+{
+    switch(type)
+    {
+    case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT;
+    case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT;
+    case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT;
+    case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT;
+    case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT;
+    case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT;
+    case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT;
+    case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT;
+    case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT;
+    }
+    throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))};
+}
+
+constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum
+{
+    switch(severity)
+    {
+    case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT;
+    case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT;
+    case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT;
+    case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT;
+    }
+    throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))};
+}
+
+
+constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char*
+{
+    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";
+    }
+    return "<invalid source>";
+}
+
+constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char*
+{
+    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";
+    }
+    return "<invalid type>";
+}
+
+constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char*
+{
+    switch(severity)
+    {
+    case DebugSeverity::High: return "High";
+    case DebugSeverity::Medium: return "Medium";
+    case DebugSeverity::Low: return "Low";
+    case DebugSeverity::Notification: return "Notification";
+    }
+    return "<invalid severity>";
+}
+
+} // namespace
+
+
+void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
+    DebugType type, ALuint id, DebugSeverity severity, std::string_view message)
+{
+    if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY
+        return;
+
+    if(message.length() >= MaxDebugMessageLength) UNLIKELY
+    {
+        ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(),
+            MaxDebugMessageLength, al::sizei(message), message.data());
+        return;
+    }
+
+    DebugGroup &debug = mDebugGroups.back();
+
+    const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source)))
+        | (1_u64 << (DebugTypeBase+al::to_underlying(type)))
+        | (uint64_t{id} << 32)};
+    auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter);
+    if(iditer != debug.mIdFilters.cend() && *iditer == idfilter)
+        return;
+
+    const uint filter{(1u << (DebugSourceBase+al::to_underlying(source)))
+        | (1u << (DebugTypeBase+al::to_underlying(type)))
+        | (1u << (DebugSeverityBase+al::to_underlying(severity)))};
+    auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter);
+    if(iter != debug.mFilters.cend() && *iter == filter)
+        return;
+
+    if(mDebugCb)
+    {
+        auto callback = mDebugCb;
+        auto param = mDebugParam;
+        debuglock.unlock();
+        callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id,
+            GetDebugSeverityEnum(severity), static_cast<ALsizei>(message.length()), message.data(),
+            param);
+    }
+    else
+    {
+        if(mDebugLog.size() < MaxDebugLoggedMessages)
+            mDebugLog.emplace_back(source, type, id, severity, message);
+        else UNLIKELY
+            ERR("Debug message log overflow. Lost message:\n"
+                "  Source: %s\n"
+                "  Type: %s\n"
+                "  ID: %u\n"
+                "  Severity: %s\n"
+                "  Message: \"%.*s\"\n",
+                GetDebugSourceName(source), GetDebugTypeName(type), id,
+                GetDebugSeverityName(severity), al::sizei(message), message.data());
+    }
+}
+
+
+FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam)
+FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context,
+    ALDEBUGPROCEXT callback, void *userParam) noexcept
+{
+    std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
+    context->mDebugCb = callback;
+    context->mDebugParam = userParam;
+}
+
+
+FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message)
+FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source,
+    ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept
+try {
+    if(!context->mContextFlags.test(ContextFlags::DebugBit))
+        return;
+
+    if(!message)
+        throw al::context_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};
+
+    auto dsource = GetDebugSource(source);
+    if(!dsource)
+        throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
+    if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
+        throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source};
+
+    auto dtype = GetDebugType(type);
+    if(!dtype)
+        throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type};
+
+    auto dseverity = GetDebugSeverity(severity);
+    if(!dseverity)
+        throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity};
+
+    context->debugMessage(*dsource, *dtype, id, *dseverity, msgview);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+
+FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable)
+FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source,
+    ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept
+try {
+    if(count > 0)
+    {
+        if(!ids)
+            throw al::context_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"};
+        if(type == AL_DONT_CARE_EXT)
+            throw al::context_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"};
+    }
+
+    if(enable != AL_TRUE && enable != AL_FALSE)
+        throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable};
+
+    static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount};
+    static constexpr auto Values = make_array_sequence<uint8_t,ElemCount>();
+
+    auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount);
+    if(source != AL_DONT_CARE_EXT)
+    {
+        auto dsource = GetDebugSource(source);
+        if(!dsource)
+            throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
+        srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1);
+    }
+
+    auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount);
+    if(type != AL_DONT_CARE_EXT)
+    {
+        auto dtype = GetDebugType(type);
+        if(!dtype)
+            throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type};
+        typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1);
+    }
+
+    auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount);
+    if(severity != AL_DONT_CARE_EXT)
+    {
+        auto dseverity = GetDebugSeverity(severity);
+        if(!dseverity)
+            throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity};
+        svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1);
+    }
+
+    std::lock_guard<std::mutex> debuglock{context->mDebugCbLock};
+    DebugGroup &debug = context->mDebugGroups.back();
+    if(count > 0)
+    {
+        const uint filterbase{(1u<<srcIndices[0]) | (1u<<typeIndices[0])};
+
+        for(const uint id : al::span{ids, static_cast<uint>(count)})
+        {
+            const uint64_t filter{filterbase | (uint64_t{id} << 32)};
+
+            auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(),
+                filter);
+            if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter))
+                debug.mIdFilters.insert(iter, filter);
+            else if(enable && iter != debug.mIdFilters.cend() && *iter == filter)
+                debug.mIdFilters.erase(iter);
+        }
+    }
+    else
+    {
+        auto apply_filter = [enable,&debug](const uint filter)
+        {
+            auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter);
+            if(!enable && (iter == debug.mFilters.cend() || *iter != filter))
+                debug.mFilters.insert(iter, filter);
+            else if(enable && iter != debug.mFilters.cend() && *iter == filter)
+                debug.mFilters.erase(iter);
+        };
+        auto apply_severity = [apply_filter,svrIndices](const uint filter)
+        {
+            std::for_each(svrIndices.cbegin(), svrIndices.cend(),
+                [apply_filter,filter](const uint idx){ apply_filter(filter | (1<<idx)); });
+        };
+        auto apply_type = [apply_severity,typeIndices](const uint filter)
+        {
+            std::for_each(typeIndices.cbegin(), typeIndices.cend(),
+                [apply_severity,filter](const uint idx){ apply_severity(filter | (1<<idx)); });
+        };
+        std::for_each(srcIndices.cbegin(), srcIndices.cend(),
+            [apply_type](const uint idx){ apply_type(1<<idx); });
+    }
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+
+FORCE_ALIGN DECL_FUNCEXT4(void, alPushDebugGroup,EXT, ALenum,source, ALuint,id, ALsizei,length, const ALchar*,message)
+FORCE_ALIGN void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source,
+    ALuint id, ALsizei length, const ALchar *message) noexcept
+try {
+    if(length < 0)
+    {
+        size_t newlen{std::strlen(message)};
+        if(newlen >= MaxDebugMessageLength)
+            throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", 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};
+
+    auto dsource = GetDebugSource(source);
+    if(!dsource)
+        throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source};
+    if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application)
+        throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", 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->mDebugGroups.emplace_back(*dsource, id,
+        std::string_view{message, static_cast<uint>(length)});
+    auto &oldback = *(context->mDebugGroups.end()-2);
+    auto &newback = context->mDebugGroups.back();
+
+    newback.mFilters = oldback.mFilters;
+    newback.mIdFilters = oldback.mIdFilters;
+
+    if(context->mContextFlags.test(ContextFlags::DebugBit))
+        context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId,
+            DebugSeverity::Notification, newback.mMessage);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT)
+FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept
+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"};
+
+    DebugGroup &debug = context->mDebugGroups.back();
+    const auto source = debug.mSource;
+    const auto id = debug.mId;
+    std::string message{std::move(debug.mMessage)};
+
+    context->mDebugGroups.pop_back();
+    if(context->mContextFlags.test(ContextFlags::DebugBit))
+        context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id,
+            DebugSeverity::Notification, message);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+
+FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf)
+FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count,
+    ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities,
+    ALsizei *lengths, ALchar *logBuf) noexcept
+try {
+    if(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};
+    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)
+        {
+            if(logOut.size() < tocopy)
+                return i;
+            auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin());
+            *oiter = '\0';
+            logOut = {oiter+1, logOut.end()};
+        }
+
+        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>();
+        }
+
+        context->mDebugLog.pop_front();
+    }
+
+    return count;
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+    return 0;
+}
+
+FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label)
+FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier,
+    ALuint name, ALsizei length, const ALchar *label) noexcept
+try {
+    if(!label && length != 0)
+        throw al::context_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};
+
+    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);
+
+    throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label)
+FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier,
+    ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept
+try {
+    if(bufSize < 0)
+        throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"};
+
+    if(!label && !length)
+        throw al::context_error{AL_INVALID_VALUE, "Null length and label"};
+    if(label && bufSize == 0)
+        throw al::context_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)
+    {
+        std::string_view objname;
+
+        auto iter = names.find(name);
+        if(iter != names.end())
+            objname = iter->second;
+
+        if(labelOut.empty())
+            *length = static_cast<ALsizei>(objname.size());
+        else
+        {
+            const size_t tocopy{std::min(objname.size(), labelOut.size()-1)};
+            auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin());
+            *oiter = '\0';
+            if(length)
+                *length = static_cast<ALsizei>(tocopy);
+        }
+    };
+
+    if(identifier == AL_SOURCE_EXT)
+    {
+        std::lock_guard srclock{context->mSourceLock};
+        copy_name(context->mSourceNames);
+    }
+    else if(identifier == AL_BUFFER)
+    {
+        ALCdevice *device{context->mALDevice.get()};
+        std::lock_guard buflock{device->BufferLock};
+        copy_name(device->mBufferNames);
+    }
+    else if(identifier == AL_FILTER_EXT)
+    {
+        ALCdevice *device{context->mALDevice.get()};
+        std::lock_guard filterlock{device->FilterLock};
+        copy_name(device->mFilterNames);
+    }
+    else if(identifier == AL_EFFECT_EXT)
+    {
+        ALCdevice *device{context->mALDevice.get()};
+        std::lock_guard effectlock{device->EffectLock};
+        copy_name(device->mEffectNames);
+    }
+    else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT)
+    {
+        std::lock_guard slotlock{context->mEffectSlotLock};
+        copy_name(context->mEffectSlotNames);
+    }
+    else
+        throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}

+ 70 - 0
libs/openal-soft/al/debug.h

@@ -0,0 +1,70 @@
+#ifndef AL_DEBUG_H
+#define AL_DEBUG_H
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+using uint = unsigned int;
+
+
+/* Somewhat arbitrary. Avoid letting it get out of control if the app enables
+ * logging but never reads it.
+ */
+inline constexpr std::uint8_t MaxDebugLoggedMessages{64};
+inline constexpr std::uint16_t MaxDebugMessageLength{1024};
+inline constexpr std::uint8_t MaxDebugGroupDepth{64};
+inline constexpr std::uint16_t MaxObjectLabelLength{1024};
+
+
+inline constexpr uint DebugSourceBase{0};
+enum class DebugSource : std::uint8_t {
+    API = 0,
+    System,
+    ThirdParty,
+    Application,
+    Other,
+};
+inline constexpr uint DebugSourceCount{5};
+
+inline constexpr uint DebugTypeBase{DebugSourceBase + DebugSourceCount};
+enum class DebugType : std::uint8_t {
+    Error = 0,
+    DeprecatedBehavior,
+    UndefinedBehavior,
+    Portability,
+    Performance,
+    Marker,
+    PushGroup,
+    PopGroup,
+    Other,
+};
+inline constexpr uint DebugTypeCount{9};
+
+inline constexpr uint DebugSeverityBase{DebugTypeBase + DebugTypeCount};
+enum class DebugSeverity : std::uint8_t {
+    High = 0,
+    Medium,
+    Low,
+    Notification,
+};
+inline constexpr uint DebugSeverityCount{4};
+
+struct DebugGroup {
+    const uint mId;
+    const DebugSource mSource;
+    std::string mMessage;
+    std::vector<uint> mFilters;
+    std::vector<std::uint64_t> mIdFilters;
+
+    template<typename T>
+    DebugGroup(DebugSource source, uint id, T&& message)
+        : mId{id}, mSource{source}, mMessage{std::forward<T>(message)}
+    { }
+    DebugGroup(const DebugGroup&) = default;
+    DebugGroup(DebugGroup&&) = default;
+    ~DebugGroup();
+};
+
+#endif /* AL_DEBUG_H */

+ 127 - 0
libs/openal-soft/al/direct_defs.h

@@ -0,0 +1,127 @@
+#ifndef AL_DIRECT_DEFS_H
+#define AL_DIRECT_DEFS_H
+
+namespace detail_ {
+
+template<typename T>
+constexpr T DefaultVal() noexcept { return T{}; }
+
+template<>
+constexpr void DefaultVal() noexcept { }
+
+} // namespace detail_
+
+#define DECL_FUNC(R, Name)                                                    \
+auto AL_APIENTRY Name() noexcept -> R                                         \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get());                                       \
+}
+
+#define DECL_FUNC1(R, Name, T1,n1)                                            \
+auto AL_APIENTRY Name(T1 n1) noexcept -> R                                    \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get(), n1);                                   \
+}
+
+#define DECL_FUNC2(R, Name, T1,n1, T2,n2)                                     \
+auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R                             \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get(), n1, n2);                               \
+}
+
+#define DECL_FUNC3(R, Name, T1,n1, T2,n2, T3,n3)                              \
+auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3) noexcept -> R                      \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get(), n1, n2, n3);                           \
+}
+
+#define DECL_FUNC4(R, Name, T1,n1, T2,n2, T3,n3, T4,n4)                       \
+auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R               \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get(), n1, n2, n3, n4);                       \
+}
+
+#define DECL_FUNC5(R, Name, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5)                \
+auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R        \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct(context.get(), n1, n2, n3, n4, n5);                   \
+}
+
+
+#define DECL_FUNCEXT(R, Name,Ext)                                             \
+auto AL_APIENTRY Name##Ext() noexcept -> R                                    \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get());                                  \
+}
+
+#define DECL_FUNCEXT1(R, Name,Ext, T1,n1)                                     \
+auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R                               \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1);                              \
+}
+
+#define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2)                              \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R                        \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2);                          \
+}
+
+#define DECL_FUNCEXT3(R, Name,Ext, T1,n1, T2,n2, T3,n3)                       \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3) noexcept -> R                 \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2, n3);                      \
+}
+
+#define DECL_FUNCEXT4(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4)                \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R          \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2, n3, n4);                  \
+}
+
+#define DECL_FUNCEXT5(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5)         \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R   \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5);              \
+}
+
+#define DECL_FUNCEXT6(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6)  \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6);          \
+}
+
+#define DECL_FUNCEXT8(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6, T7,n7, T8,n8) \
+auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \
+{                                                                             \
+    auto context = GetContextRef();                                           \
+    if(!context) UNLIKELY return detail_::DefaultVal<R>();                    \
+    return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8);  \
+}
+
+#endif /* AL_DIRECT_DEFS_H */

+ 566 - 158
libs/openal-soft/al/eax_api.cpp → libs/openal-soft/al/eax/api.cpp

@@ -9,7 +9,7 @@
 
 #include <algorithm>
 
-#include "al/eax_api.h"
+#include "api.h"
 
 
 const GUID DSPROPSETID_EAX_ReverbProperties =
@@ -269,74 +269,15 @@ const GUID EAX_RINGMODULATOR_EFFECT =
 };
 
 
-bool operator==(
-    const EAX40CONTEXTPROPERTIES& lhs,
-    const EAX40CONTEXTPROPERTIES& rhs) noexcept
-{
-    return
-        lhs.guidPrimaryFXSlotID == rhs.guidPrimaryFXSlotID &&
-        lhs.flDistanceFactor == rhs.flDistanceFactor &&
-        lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF &&
-        lhs.flHFReference == rhs.flHFReference;
-}
-
-bool operator==(
-    const EAX50CONTEXTPROPERTIES& lhs,
-    const EAX50CONTEXTPROPERTIES& rhs) noexcept
-{
-    return
-        static_cast<const EAX40CONTEXTPROPERTIES&>(lhs) == static_cast<const EAX40CONTEXTPROPERTIES&>(rhs) &&
-        lhs.flMacroFXFactor == rhs.flMacroFXFactor;
-}
-
-
-const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0;
+const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0;
+const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX50_FXSlot0;
 
-bool operator==(
-    const EAX40FXSLOTPROPERTIES& lhs,
-    const EAX40FXSLOTPROPERTIES& rhs) noexcept
-{
-    return
-        lhs.guidLoadEffect == rhs.guidLoadEffect &&
-        lhs.lVolume == rhs.lVolume &&
-        lhs.lLock == rhs.lLock &&
-        lhs.ulFlags == rhs.ulFlags;
-}
-
-bool operator==(
-    const EAX50FXSLOTPROPERTIES& lhs,
-    const EAX50FXSLOTPROPERTIES& rhs) noexcept
-{
-    return
-        static_cast<const EAX40FXSLOTPROPERTIES&>(lhs) == static_cast<const EAX40FXSLOTPROPERTIES&>(rhs) &&
-        lhs.lOcclusion == rhs.lOcclusion &&
-        lhs.flOcclusionLFRatio == rhs.flOcclusionLFRatio;
-}
-
-const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
+const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX40ACTIVEFXSLOTS
 {{
     EAX_NULL_GUID,
     EAXPROPERTYID_EAX40_FXSlot0,
 }};
 
-bool operator==(
-    const EAX50ACTIVEFXSLOTS& lhs,
-    const EAX50ACTIVEFXSLOTS& rhs) noexcept
-{
-    return std::equal(
-        std::cbegin(lhs.guidActiveFXSlots),
-        std::cend(lhs.guidActiveFXSlots),
-        std::begin(rhs.guidActiveFXSlots));
-}
-
-bool operator!=(
-    const EAX50ACTIVEFXSLOTS& lhs,
-    const EAX50ACTIVEFXSLOTS& rhs) noexcept
-{
-    return !(lhs == rhs);
-}
-
-
 const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
 {{
     EAX_NULL_GUID,
@@ -354,44 +295,569 @@ const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOT
     EAX_NULL_GUID,
 }};
 
-bool operator==(
-    const EAXREVERBPROPERTIES& lhs,
-    const EAXREVERBPROPERTIES& rhs) noexcept
-{
-    return
-        lhs.ulEnvironment == rhs.ulEnvironment &&
-        lhs.flEnvironmentSize == rhs.flEnvironmentSize &&
-        lhs.flEnvironmentDiffusion == rhs.flEnvironmentDiffusion &&
-        lhs.lRoom == rhs.lRoom &&
-        lhs.lRoomHF == rhs.lRoomHF &&
-        lhs.lRoomLF == rhs.lRoomLF &&
-        lhs.flDecayTime == rhs.flDecayTime &&
-        lhs.flDecayHFRatio == rhs.flDecayHFRatio &&
-        lhs.flDecayLFRatio == rhs.flDecayLFRatio &&
-        lhs.lReflections == rhs.lReflections &&
-        lhs.flReflectionsDelay == rhs.flReflectionsDelay &&
-        lhs.vReflectionsPan == rhs.vReflectionsPan &&
-        lhs.lReverb == rhs.lReverb &&
-        lhs.flReverbDelay == rhs.flReverbDelay &&
-        lhs.vReverbPan == rhs.vReverbPan &&
-        lhs.flEchoTime == rhs.flEchoTime &&
-        lhs.flEchoDepth == rhs.flEchoDepth &&
-        lhs.flModulationTime == rhs.flModulationTime &&
-        lhs.flModulationDepth == rhs.flModulationDepth &&
-        lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF &&
-        lhs.flHFReference == rhs.flHFReference &&
-        lhs.flLFReference == rhs.flLFReference &&
-        lhs.flRoomRolloffFactor == rhs.flRoomRolloffFactor &&
-        lhs.ulFlags == rhs.ulFlags;
-}
-
-bool operator!=(
-    const EAXREVERBPROPERTIES& lhs,
-    const EAXREVERBPROPERTIES& rhs) noexcept
-{
-    return !(lhs == rhs);
-}
 
+// EAX1 =====================================================================
+
+namespace {
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F};
+constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F};
+} // namespace
+
+const Eax1ReverbPresets EAX1REVERB_PRESETS{{
+    EAX1REVERB_PRESET_GENERIC,
+    EAX1REVERB_PRESET_PADDEDCELL,
+    EAX1REVERB_PRESET_ROOM,
+    EAX1REVERB_PRESET_BATHROOM,
+    EAX1REVERB_PRESET_LIVINGROOM,
+    EAX1REVERB_PRESET_STONEROOM,
+    EAX1REVERB_PRESET_AUDITORIUM,
+    EAX1REVERB_PRESET_CONCERTHALL,
+    EAX1REVERB_PRESET_CAVE,
+    EAX1REVERB_PRESET_ARENA,
+    EAX1REVERB_PRESET_HANGAR,
+    EAX1REVERB_PRESET_CARPETTEDHALLWAY,
+    EAX1REVERB_PRESET_HALLWAY,
+    EAX1REVERB_PRESET_STONECORRIDOR,
+    EAX1REVERB_PRESET_ALLEY,
+    EAX1REVERB_PRESET_FOREST,
+    EAX1REVERB_PRESET_CITY,
+    EAX1REVERB_PRESET_MOUNTAINS,
+    EAX1REVERB_PRESET_QUARRY,
+    EAX1REVERB_PRESET_PLAIN,
+    EAX1REVERB_PRESET_PARKINGLOT,
+    EAX1REVERB_PRESET_SEWERPIPE,
+    EAX1REVERB_PRESET_UNDERWATER,
+    EAX1REVERB_PRESET_DRUGGED,
+    EAX1REVERB_PRESET_DIZZY,
+    EAX1REVERB_PRESET_PSYCHOTIC,
+}};
+
+// EAX2 =====================================================================
+
+namespace {
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_GENERIC{
+    EAX2LISTENER_DEFAULTROOM,
+    EAX2LISTENER_DEFAULTROOMHF,
+    EAX2LISTENER_DEFAULTROOMROLLOFFFACTOR,
+    EAX2LISTENER_DEFAULTDECAYTIME,
+    EAX2LISTENER_DEFAULTDECAYHFRATIO,
+    EAX2LISTENER_DEFAULTREFLECTIONS,
+    EAX2LISTENER_DEFAULTREFLECTIONSDELAY,
+    EAX2LISTENER_DEFAULTREVERB,
+    EAX2LISTENER_DEFAULTREVERBDELAY,
+    EAX2LISTENER_DEFAULTENVIRONMENT,
+    EAX2LISTENER_DEFAULTENVIRONMENTSIZE,
+    EAX2LISTENER_DEFAULTENVIRONMENTDIFFUSION,
+    EAX2LISTENER_DEFAULTAIRABSORPTIONHF,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PADDEDCELL{
+    -1'000L,
+    -6'000L,
+    0.0F,
+    0.17F,
+    0.1F,
+    -1'204L,
+    0.001F,
+    207L,
+    0.002F,
+    EAX2_ENVIRONMENT_PADDEDCELL,
+    1.4F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ROOM{
+    -1'000L,
+    -454L,
+    0.0F,
+    0.4F,
+    0.83F,
+    -1'646L,
+    0.002F,
+    53L,
+    0.003F,
+    EAX2_ENVIRONMENT_ROOM,
+    1.9F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_BATHROOM{
+    -1'000L,
+    -1'200L,
+    0.0F,
+    1.49F,
+    0.54F,
+    -370L,
+    0.007F,
+    1'030L,
+    0.011F,
+    EAX2_ENVIRONMENT_BATHROOM,
+    1.4F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_LIVINGROOM{
+    -1'000L,
+    -6'000L,
+    0.0F,
+    0.5F,
+    0.1F,
+    -1'376L,
+    0.003F,
+    -1'104L,
+    0.004F,
+    EAX2_ENVIRONMENT_LIVINGROOM,
+    2.5F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONEROOM{
+    -1'000L,
+    -300L,
+    0.0F,
+    2.31F,
+    0.64F,
+    -711L,
+    0.012F,
+    83L,
+    0.017F,
+    EAX2_ENVIRONMENT_STONEROOM,
+    11.6F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_AUDITORIUM{
+    -1'000L,
+    -476L,
+    0.0F,
+    4.32F,
+    0.59F,
+    -789L,
+    0.02F,
+    -289L,
+    0.03F,
+    EAX2_ENVIRONMENT_AUDITORIUM,
+    21.6F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CONCERTHALL{
+    -1'000L,
+    -500L,
+    0.0F,
+    3.92F,
+    0.7F,
+    -1'230L,
+    0.02F,
+    -2L,
+    0.029F,
+    EAX2_ENVIRONMENT_CONCERTHALL,
+    19.6F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CAVE{
+    -1'000L,
+    0L,
+    0.0F,
+    2.91F,
+    1.3F,
+    -602L,
+    0.015F,
+    -302L,
+    0.022F,
+    EAX2_ENVIRONMENT_CAVE,
+    14.6F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ARENA{
+    -1'000L,
+    -698L,
+    0.0F,
+    7.24F,
+    0.33F,
+    -1'166L,
+    0.02F,
+    16L,
+    0.03F,
+    EAX2_ENVIRONMENT_ARENA,
+    36.2F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HANGAR{
+    -1'000L,
+    -1'000L,
+    0.0F,
+    10.05F,
+    0.23F,
+    -602L,
+    0.02F,
+    198L,
+    0.03F,
+    EAX2_ENVIRONMENT_HANGAR,
+    50.3F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CARPETTEDHALLWAY{
+    -1'000L,
+    -4'000L,
+    0.0F,
+    0.3F,
+    0.1F,
+    -1'831L,
+    0.002F,
+    -1'630L,
+    0.03F,
+    EAX2_ENVIRONMENT_CARPETEDHALLWAY,
+    1.9F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HALLWAY{
+    -1'000L,
+    -300L,
+    0.0F,
+    1.49F,
+    0.59F,
+    -1'219L,
+    0.007F,
+    441L,
+    0.011F,
+    EAX2_ENVIRONMENT_HALLWAY,
+    1.8F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONECORRIDOR{
+    -1'000L,
+    -237L,
+    0.0F,
+    2.7F,
+    0.79F,
+    -1'214L,
+    0.013F,
+    395L,
+    0.02F,
+    EAX2_ENVIRONMENT_STONECORRIDOR,
+    13.5F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ALLEY{
+    -1'000L,
+    -270L,
+    0.0F,
+    1.49F,
+    0.86F,
+    -1'204L,
+    0.007F,
+    -4L,
+    0.011F,
+    EAX2_ENVIRONMENT_ALLEY,
+    7.5F,
+    0.3F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_FOREST{
+    -1'000L,
+    -3'300L,
+    0.0F,
+    1.49F,
+    0.54F,
+    -2'560L,
+    0.162F,
+    -229L,
+    0.088F,
+    EAX2_ENVIRONMENT_FOREST,
+    38.0F,
+    0.3F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CITY{
+    -1'000L,
+    -800L,
+    0.0F,
+    1.49F,
+    0.67F,
+    -2'273L,
+    0.007F,
+    -1'691L,
+    0.011F,
+    EAX2_ENVIRONMENT_CITY,
+    7.5F,
+    0.5F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_MOUNTAINS{
+    -1'000L,
+    -2'500L,
+    0.0F,
+    1.49F,
+    0.21F,
+    -2'780L,
+    0.3F,
+    -1'434L,
+    0.1F,
+    EAX2_ENVIRONMENT_MOUNTAINS,
+    100.0F,
+    0.27F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_QUARRY{
+    -1'000L,
+    -1'000L,
+    0.0F,
+    1.49F,
+    0.83F,
+    -10'000L,
+    0.061F,
+    500L,
+    0.025F,
+    EAX2_ENVIRONMENT_QUARRY,
+    17.5F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PLAIN{
+    -1'000L,
+    -2'000L,
+    0.0F,
+    1.49F,
+    0.5F,
+    -2'466L,
+    0.179F,
+    -1'926L,
+    0.1F,
+    EAX2_ENVIRONMENT_PLAIN,
+    42.5F,
+    0.21F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PARKINGLOT{
+    -1'000L,
+    0L,
+    0.0F,
+    1.65F,
+    1.5F,
+    -1'363L,
+    0.008F,
+    -1'153L,
+    0.012F,
+    EAX2_ENVIRONMENT_PARKINGLOT,
+    8.3F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_SEWERPIPE{
+    -1'000L,
+    -1'000L,
+    0.0F,
+    2.81F,
+    0.14F,
+    429L,
+    0.014F,
+    1'023L,
+    0.021F,
+    EAX2_ENVIRONMENT_SEWERPIPE,
+    1.7F,
+    0.8F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_UNDERWATER{
+    -1'000L,
+    -4'000L,
+    0.0F,
+    1.49F,
+    0.1F,
+    -449L,
+    0.007F,
+    1'700L,
+    0.011F,
+    EAX2_ENVIRONMENT_UNDERWATER,
+    1.8F,
+    1.0F,
+    -5.0F,
+    EAX2LISTENER_DEFAULTFLAGS,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DRUGGED{
+    -1'000L,
+    0L,
+    0.0F,
+    8.39F,
+    1.39F,
+    -115L,
+    0.002F,
+    985L,
+    0.03F,
+    EAX2_ENVIRONMENT_DRUGGED,
+    1.9F,
+    0.5F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DIZZY{
+    -1'000L,
+    -400L,
+    0.0F,
+    17.23F,
+    0.56F,
+    -1'713L,
+    0.02F,
+    -613L,
+    0.03F,
+    EAX2_ENVIRONMENT_DIZZY,
+    1.8F,
+    0.6F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PSYCHOTIC{
+    -1'000L,
+    -151L,
+    0.0F,
+    7.56F,
+    0.91F,
+    -626L,
+    0.02F,
+    774L,
+    0.03F,
+    EAX2_ENVIRONMENT_PSYCHOTIC,
+    1.0F,
+    0.5F,
+    -5.0F,
+    EAX2LISTENERFLAGS_DECAYTIMESCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSSCALE |
+        EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE |
+        EAX2LISTENERFLAGS_REVERBSCALE |
+        EAX2LISTENERFLAGS_REVERBDELAYSCALE,
+};
+
+} // namespace
+
+const Eax2ReverbPresets EAX2REVERB_PRESETS{
+    EAX2REVERB_PRESET_GENERIC,
+    EAX2REVERB_PRESET_PADDEDCELL,
+    EAX2REVERB_PRESET_ROOM,
+    EAX2REVERB_PRESET_BATHROOM,
+    EAX2REVERB_PRESET_LIVINGROOM,
+    EAX2REVERB_PRESET_STONEROOM,
+    EAX2REVERB_PRESET_AUDITORIUM,
+    EAX2REVERB_PRESET_CONCERTHALL,
+    EAX2REVERB_PRESET_CAVE,
+    EAX2REVERB_PRESET_ARENA,
+    EAX2REVERB_PRESET_HANGAR,
+    EAX2REVERB_PRESET_CARPETTEDHALLWAY,
+    EAX2REVERB_PRESET_HALLWAY,
+    EAX2REVERB_PRESET_STONECORRIDOR,
+    EAX2REVERB_PRESET_ALLEY,
+    EAX2REVERB_PRESET_FOREST,
+    EAX2REVERB_PRESET_CITY,
+    EAX2REVERB_PRESET_MOUNTAINS,
+    EAX2REVERB_PRESET_QUARRY,
+    EAX2REVERB_PRESET_PLAIN,
+    EAX2REVERB_PRESET_PARKINGLOT,
+    EAX2REVERB_PRESET_SEWERPIPE,
+    EAX2REVERB_PRESET_UNDERWATER,
+    EAX2REVERB_PRESET_DRUGGED,
+    EAX2REVERB_PRESET_DIZZY,
+    EAX2REVERB_PRESET_PSYCHOTIC,
+};
+
+// EAX3+ ====================================================================
 
 namespace {
 
@@ -1153,61 +1619,3 @@ const EaxReverbPresets EAXREVERB_PRESETS{{
     EAXREVERB_PRESET_DIZZY,
     EAXREVERB_PRESET_PSYCHOTIC,
 }};
-
-namespace {
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F};
-constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F};
-} // namespace
-
-const Eax1ReverbPresets EAX1REVERB_PRESETS{{
-    EAX1REVERB_PRESET_GENERIC,
-    EAX1REVERB_PRESET_PADDEDCELL,
-    EAX1REVERB_PRESET_ROOM,
-    EAX1REVERB_PRESET_BATHROOM,
-    EAX1REVERB_PRESET_LIVINGROOM,
-    EAX1REVERB_PRESET_STONEROOM,
-    EAX1REVERB_PRESET_AUDITORIUM,
-    EAX1REVERB_PRESET_CONCERTHALL,
-    EAX1REVERB_PRESET_CAVE,
-    EAX1REVERB_PRESET_ARENA,
-    EAX1REVERB_PRESET_HANGAR,
-    EAX1REVERB_PRESET_CARPETTEDHALLWAY,
-    EAX1REVERB_PRESET_HALLWAY,
-    EAX1REVERB_PRESET_STONECORRIDOR,
-    EAX1REVERB_PRESET_ALLEY,
-    EAX1REVERB_PRESET_FOREST,
-    EAX1REVERB_PRESET_CITY,
-    EAX1REVERB_PRESET_MOUNTAINS,
-    EAX1REVERB_PRESET_QUARRY,
-    EAX1REVERB_PRESET_PLAIN,
-    EAX1REVERB_PRESET_PARKINGLOT,
-    EAX1REVERB_PRESET_SEWERPIPE,
-    EAX1REVERB_PRESET_UNDERWATER,
-    EAX1REVERB_PRESET_DRUGGED,
-    EAX1REVERB_PRESET_DIZZY,
-    EAX1REVERB_PRESET_PSYCHOTIC,
-}};

文件差异内容过多而无法显示
+ 221 - 250
libs/openal-soft/al/eax/api.h


+ 218 - 0
libs/openal-soft/al/eax/call.cpp

@@ -0,0 +1,218 @@
+#include "config.h"
+#include "call.h"
+#include "exception.h"
+
+namespace {
+
+constexpr auto deferred_flag = 0x80000000U;
+
+class EaxCallException : public EaxException {
+public:
+    explicit EaxCallException(const char* message)
+        : EaxException{"EAX_CALL", message}
+    {}
+}; // EaxCallException
+
+} // namespace
+
+EaxCall::EaxCall(
+    EaxCallType type,
+    const GUID& property_set_guid,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_buffer,
+    ALuint property_size)
+    : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0}
+    , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id}
+    , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size}
+{
+    switch(mCallType)
+    {
+        case EaxCallType::get:
+        case EaxCallType::set:
+            break;
+
+        default:
+            fail("Invalid type.");
+    }
+
+    if (false)
+    {
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_Context)
+    {
+        mVersion = 4;
+        mPropertySetId = EaxCallPropertySetId::context;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_Context)
+    {
+        mVersion = 5;
+        mPropertySetId = EaxCallPropertySetId::context;
+    }
+    else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties)
+    {
+        mVersion = 2;
+        mFxSlotIndex = 0u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
+    }
+    else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties)
+    {
+        mVersion = 3;
+        mFxSlotIndex = 0u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0)
+    {
+        mVersion = 4;
+        mFxSlotIndex = 0u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0)
+    {
+        mVersion = 5;
+        mFxSlotIndex = 0u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1)
+    {
+        mVersion = 4;
+        mFxSlotIndex = 1u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1)
+    {
+        mVersion = 5;
+        mFxSlotIndex = 1u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2)
+    {
+        mVersion = 4;
+        mFxSlotIndex = 2u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2)
+    {
+        mVersion = 5;
+        mFxSlotIndex = 2u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3)
+    {
+        mVersion = 4;
+        mFxSlotIndex = 3u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3)
+    {
+        mVersion = 5;
+        mFxSlotIndex = 3u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot;
+    }
+    else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties)
+    {
+        mVersion = 2;
+        mPropertySetId = EaxCallPropertySetId::source;
+    }
+    else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties)
+    {
+        mVersion = 3;
+        mPropertySetId = EaxCallPropertySetId::source;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX40_Source)
+    {
+        mVersion = 4;
+        mPropertySetId = EaxCallPropertySetId::source;
+    }
+    else if (property_set_guid == EAXPROPERTYID_EAX50_Source)
+    {
+        mVersion = 5;
+        mPropertySetId = EaxCallPropertySetId::source;
+    }
+    else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties)
+    {
+        mVersion = 1;
+        mFxSlotIndex = 0u;
+        mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
+    }
+    else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties)
+    {
+        mVersion = 1;
+        mPropertySetId = EaxCallPropertySetId::source;
+    }
+    else
+    {
+        fail("Unsupported property set id.");
+    }
+
+    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(!mIsDeferred)
+    {
+        if(mPropertySetId != EaxCallPropertySetId::fx_slot && mPropertyId != 0)
+        {
+            if(mPropertyBuffer == nullptr)
+                fail("Null property buffer.");
+
+            if(mPropertyBufferSize == 0)
+                fail("Empty property.");
+        }
+    }
+
+    if(mPropertySetId == EaxCallPropertySetId::source && mPropertySourceId == 0)
+        fail("Null AL source id.");
+
+    if(mPropertySetId == EaxCallPropertySetId::fx_slot)
+    {
+        if(mPropertyId < EAXFXSLOT_NONE)
+            mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
+    }
+}
+
+[[noreturn]] void EaxCall::fail(const char* message)
+{
+    throw EaxCallException{message};
+}
+
+[[noreturn]] void EaxCall::fail_too_small()
+{
+    fail("Property buffer too small.");
+}
+
+EaxCall create_eax_call(
+    EaxCallType type,
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_buffer,
+    ALuint property_size)
+{
+    if(!property_set_id)
+        throw EaxCallException{"Null property set ID."};
+
+    return EaxCall{
+        type,
+        *property_set_id,
+        property_id,
+        property_source_id,
+        property_buffer,
+        property_size
+    };
+}

+ 97 - 0
libs/openal-soft/al/eax/call.h

@@ -0,0 +1,97 @@
+#ifndef EAX_EAX_CALL_INCLUDED
+#define EAX_EAX_CALL_INCLUDED
+
+#include "AL/al.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "api.h"
+#include "fx_slot_index.h"
+
+enum class EaxCallType {
+    none,
+    get,
+    set,
+}; // EaxCallType
+
+enum class EaxCallPropertySetId {
+    none,
+    context,
+    fx_slot,
+    source,
+    fx_slot_effect,
+}; // EaxCallPropertySetId
+
+class EaxCall {
+public:
+    EaxCall(
+        EaxCallType type,
+        const GUID& property_set_guid,
+        ALuint property_id,
+        ALuint property_source_id,
+        ALvoid* property_buffer,
+        ALuint property_size);
+
+    [[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; }
+    [[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; }
+    [[nodiscard]] auto get_version() const noexcept -> int { return mVersion; }
+    [[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; }
+    [[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; }
+    [[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; }
+    [[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; }
+
+    template<typename TException, typename TValue>
+    [[nodiscard]] auto get_value() const -> TValue&
+    {
+        if(mPropertyBufferSize < sizeof(TValue))
+            fail_too_small();
+
+        return *static_cast<TValue*>(mPropertyBuffer);
+    }
+
+    template<typename TValue>
+    [[nodiscard]] auto get_values(size_t max_count) const -> al::span<TValue>
+    {
+        if(max_count == 0 || mPropertyBufferSize < sizeof(TValue))
+            fail_too_small();
+
+        const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count);
+        return {static_cast<TValue*>(mPropertyBuffer), count};
+    }
+
+    template<typename TValue>
+    [[nodiscard]] auto get_values() const -> al::span<TValue>
+    {
+        return get_values<TValue>(~0_uz);
+    }
+
+    template<typename TException, typename TValue>
+    auto set_value(const TValue& value) const -> void
+    {
+        get_value<TException, TValue>() = value;
+    }
+
+private:
+    const EaxCallType mCallType;
+    int mVersion{};
+    EaxFxSlotIndex mFxSlotIndex{};
+    EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none};
+    bool mIsDeferred;
+
+    const ALuint mPropertyId;
+    const ALuint mPropertySourceId;
+    ALvoid*const mPropertyBuffer;
+    const ALuint mPropertyBufferSize;
+
+    [[noreturn]] static void fail(const char* message);
+    [[noreturn]] static void fail_too_small();
+}; // EaxCall
+
+EaxCall create_eax_call(
+    EaxCallType type,
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_buffer,
+    ALuint property_size);
+
+#endif // !EAX_EAX_CALL_INCLUDED

+ 457 - 0
libs/openal-soft/al/eax/effect.h

@@ -0,0 +1,457 @@
+#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"
+
+struct EaxEffectErrorMessages {
+    static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; }
+    static constexpr auto unknown_version() noexcept { return "Unknown version."; }
+}; // EaxEffectErrorMessages
+
+using EaxEffectProps = std::variant<std::monostate,
+    EAXREVERBPROPERTIES,
+    EAXCHORUSPROPERTIES,
+    EAXAUTOWAHPROPERTIES,
+    EAXAGCCOMPRESSORPROPERTIES,
+    EAXDISTORTIONPROPERTIES,
+    EAXECHOPROPERTIES,
+    EAXEQUALIZERPROPERTIES,
+    EAXFLANGERPROPERTIES,
+    EAXFREQUENCYSHIFTERPROPERTIES,
+    EAXRINGMODULATORPROPERTIES,
+    EAXPITCHSHIFTERPROPERTIES,
+    EAXVOCALMORPHERPROPERTIES>;
+
+template<typename... Ts>
+struct overloaded : Ts... { using Ts::operator()...; };
+
+template<typename... Ts>
+overloaded(Ts...) -> overloaded<Ts...>;
+
+constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props)
+{
+    return std::visit(overloaded{
+        [](const std::monostate&) noexcept { return AL_EFFECT_NULL; },
+        [](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; },
+        [](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; },
+        [](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; },
+        [](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; },
+        [](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; },
+        [](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; },
+        [](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; },
+        [](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; },
+        [](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; },
+        [](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; },
+        [](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; },
+        [](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; }
+    }, props);
+}
+
+struct EaxReverbCommitter {
+    struct Exception;
+
+    EaxReverbCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
+        : mEaxProps{eaxprops}, mAlProps{alprops}
+    { }
+
+    EaxEffectProps &mEaxProps;
+    EffectProps &mAlProps;
+
+    [[noreturn]] static void fail(const char* message);
+    [[noreturn]] static void fail_unknown_property_id()
+    { fail(EaxEffectErrorMessages::unknown_property_id()); }
+
+    template<typename TValidator, typename TProperty>
+    static void defer(const EaxCall& call, TProperty& property)
+    {
+        const auto& value = call.get_value<Exception, const TProperty>();
+        TValidator{}(value);
+        property = value;
+    }
+
+    template<typename TValidator, typename TDeferrer, typename TProperties, typename TProperty>
+    static void defer(const EaxCall& call, TProperties& properties, TProperty&)
+    {
+        const auto& value = call.get_value<Exception, const TProperty>();
+        TValidator{}(value);
+        TDeferrer{}(properties, value);
+    }
+
+    template<typename TValidator, typename TProperty>
+    static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property)
+    {
+        const auto& value = call.get_value<Exception, const TProperty>();
+        TValidator{}(value);
+        if (value == property)
+            return;
+        property = value;
+        properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED;
+    }
+
+
+    bool commit(const EAX_REVERBPROPERTIES &props);
+    bool commit(const EAX20LISTENERPROPERTIES &props);
+    bool commit(const EAXREVERBPROPERTIES &props);
+
+    static void SetDefaults(EAX_REVERBPROPERTIES &props);
+    static void SetDefaults(EAX20LISTENERPROPERTIES &props);
+    static void SetDefaults(EAXREVERBPROPERTIES &props);
+    static void SetDefaults(EaxEffectProps &props);
+
+    static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props);
+    static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props);
+    static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props);
+
+    static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props);
+    static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props);
+
+    static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept;
+    static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept;
+};
+
+template<typename T>
+struct EaxCommitter {
+    struct Exception;
+
+    EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
+        : mEaxProps{eaxprops}, mAlProps{alprops}
+    { }
+
+    EaxEffectProps &mEaxProps;
+    EffectProps &mAlProps;
+
+    template<typename TValidator, typename TProperty>
+    static void defer(const EaxCall& call, TProperty& property)
+    {
+        const auto& value = call.get_value<Exception, const TProperty>();
+        TValidator{}(value);
+        property = value;
+    }
+
+    [[noreturn]] static void fail(const char *message);
+    [[noreturn]] static void fail_unknown_property_id()
+    { fail(EaxEffectErrorMessages::unknown_property_id()); }
+};
+
+struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
+    using EaxCommitter<EaxAutowahCommitter>::EaxCommitter;
+
+    bool commit(const EAXAUTOWAHPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props);
+};
+struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
+    using EaxCommitter<EaxChorusCommitter>::EaxCommitter;
+
+    bool commit(const EAXCHORUSPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props);
+};
+struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
+    using EaxCommitter<EaxCompressorCommitter>::EaxCommitter;
+
+    bool commit(const EAXAGCCOMPRESSORPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props);
+};
+struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
+    using EaxCommitter<EaxDistortionCommitter>::EaxCommitter;
+
+    bool commit(const EAXDISTORTIONPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props);
+};
+struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
+    using EaxCommitter<EaxEchoCommitter>::EaxCommitter;
+
+    bool commit(const EAXECHOPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXECHOPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXECHOPROPERTIES &props);
+};
+struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
+    using EaxCommitter<EaxEqualizerCommitter>::EaxCommitter;
+
+    bool commit(const EAXEQUALIZERPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props);
+};
+struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
+    using EaxCommitter<EaxFlangerCommitter>::EaxCommitter;
+
+    bool commit(const EAXFLANGERPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props);
+};
+struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> {
+    using EaxCommitter<EaxFrequencyShifterCommitter>::EaxCommitter;
+
+    bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props);
+};
+struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
+    using EaxCommitter<EaxModulatorCommitter>::EaxCommitter;
+
+    bool commit(const EAXRINGMODULATORPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props);
+};
+struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> {
+    using EaxCommitter<EaxPitchShifterCommitter>::EaxCommitter;
+
+    bool commit(const EAXPITCHSHIFTERPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props);
+};
+struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> {
+    using EaxCommitter<EaxVocalMorpherCommitter>::EaxCommitter;
+
+    bool commit(const EAXVOCALMORPHERPROPERTIES &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props);
+    static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props);
+};
+struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> {
+    using EaxCommitter<EaxNullCommitter>::EaxCommitter;
+
+    bool commit(const std::monostate &props);
+
+    static void SetDefaults(EaxEffectProps &props);
+    static void Get(const EaxCall &call, const std::monostate &props);
+    static void Set(const EaxCall &call, std::monostate &props);
+};
+
+template<typename T>
+struct CommitterFromProps { };
+
+template<> struct CommitterFromProps<std::monostate> { using type = EaxNullCommitter; };
+template<> struct CommitterFromProps<EAXREVERBPROPERTIES> { using type = EaxReverbCommitter; };
+template<> struct CommitterFromProps<EAXCHORUSPROPERTIES> { using type = EaxChorusCommitter; };
+template<> struct CommitterFromProps<EAXAGCCOMPRESSORPROPERTIES> { using type = EaxCompressorCommitter; };
+template<> struct CommitterFromProps<EAXAUTOWAHPROPERTIES> { using type = EaxAutowahCommitter; };
+template<> struct CommitterFromProps<EAXDISTORTIONPROPERTIES> { using type = EaxDistortionCommitter; };
+template<> struct CommitterFromProps<EAXECHOPROPERTIES> { using type = EaxEchoCommitter; };
+template<> struct CommitterFromProps<EAXEQUALIZERPROPERTIES> { using type = EaxEqualizerCommitter; };
+template<> struct CommitterFromProps<EAXFLANGERPROPERTIES> { using type = EaxFlangerCommitter; };
+template<> struct CommitterFromProps<EAXFREQUENCYSHIFTERPROPERTIES> { using type = EaxFrequencyShifterCommitter; };
+template<> struct CommitterFromProps<EAXRINGMODULATORPROPERTIES> { using type = EaxModulatorCommitter; };
+template<> struct CommitterFromProps<EAXPITCHSHIFTERPROPERTIES> { using type = EaxPitchShifterCommitter; };
+template<> struct CommitterFromProps<EAXVOCALMORPHERPROPERTIES> { using type = EaxVocalMorpherCommitter; };
+
+template<typename T>
+using CommitterFor = typename CommitterFromProps<std::remove_cv_t<std::remove_reference_t<T>>>::type;
+
+
+class EaxEffect {
+public:
+    EaxEffect() noexcept = default;
+    ~EaxEffect() = default;
+
+    ALenum al_effect_type_{AL_EFFECT_NULL};
+    EffectProps al_effect_props_{};
+
+    using Props1 = EAX_REVERBPROPERTIES;
+    using Props2 = EAX20LISTENERPROPERTIES;
+    using Props3 = EAXREVERBPROPERTIES;
+    using Props4 = EaxEffectProps;
+
+    struct State1 {
+        Props1 i; // Immediate.
+        Props1 d; // Deferred.
+    };
+
+    struct State2 {
+        Props2 i; // Immediate.
+        Props2 d; // Deferred.
+    };
+
+    struct State3 {
+        Props3 i; // Immediate.
+        Props3 d; // Deferred.
+    };
+
+    struct State4 {
+        Props4 i; // Immediate.
+        Props4 d; // Deferred.
+    };
+
+    int version_{};
+    bool changed_{};
+    Props4 props_{};
+    State1 state1_{};
+    State2 state2_{};
+    State3 state3_{};
+    State4 state4_{};
+    State4 state5_{};
+
+
+    static void call_set_defaults(const ALenum altype, EaxEffectProps &props)
+    {
+        switch(altype)
+        {
+        case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props);
+        case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props);
+        case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props);
+        case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props);
+        case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props);
+        case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props);
+        case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props);
+        case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props);
+        case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props);
+        case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props);
+        case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props);
+        case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props);
+        case AL_EFFECT_NULL: break;
+        }
+        return EaxNullCommitter::SetDefaults(props);
+    }
+
+    template<typename T>
+    void init()
+    {
+        EaxReverbCommitter::SetDefaults(state1_.d);
+        state1_.i = state1_.d;
+        EaxReverbCommitter::SetDefaults(state2_.d);
+        state2_.i = state2_.d;
+        EaxReverbCommitter::SetDefaults(state3_.d);
+        state3_.i = state3_.d;
+        T::SetDefaults(state4_.d);
+        state4_.i = state4_.d;
+        T::SetDefaults(state5_.d);
+        state5_.i = state5_.d;
+    }
+
+    void set_defaults(int eax_version, ALenum altype)
+    {
+        switch(eax_version)
+        {
+        case 1: EaxReverbCommitter::SetDefaults(state1_.d); break;
+        case 2: EaxReverbCommitter::SetDefaults(state2_.d); break;
+        case 3: EaxReverbCommitter::SetDefaults(state3_.d); break;
+        case 4: call_set_defaults(altype, state4_.d); break;
+        case 5: call_set_defaults(altype, state5_.d); break;
+        }
+        changed_ = true;
+    }
+
+
+    static void call_set(const EaxCall &call, EaxEffectProps &props)
+    {
+        return std::visit([&](auto &arg)
+        { return CommitterFor<decltype(arg)>::Set(call, arg); },
+        props);
+    }
+
+    void set(const EaxCall &call)
+    {
+        switch(call.get_version())
+        {
+        case 1: EaxReverbCommitter::Set(call, state1_.d); break;
+        case 2: EaxReverbCommitter::Set(call, state2_.d); break;
+        case 3: EaxReverbCommitter::Set(call, state3_.d); break;
+        case 4: call_set(call, state4_.d); break;
+        case 5: call_set(call, state5_.d); break;
+        }
+        changed_ = true;
+    }
+
+
+    static void call_get(const EaxCall &call, const EaxEffectProps &props)
+    {
+        return std::visit([&](auto &arg)
+        { return CommitterFor<decltype(arg)>::Get(call, arg); },
+        props);
+    }
+
+    void get(const EaxCall &call) const
+    {
+        switch(call.get_version())
+        {
+        case 1: EaxReverbCommitter::Get(call, state1_.d); break;
+        case 2: EaxReverbCommitter::Get(call, state2_.d); break;
+        case 3: EaxReverbCommitter::Get(call, state3_.d); break;
+        case 4: call_get(call, state4_.d); break;
+        case 5: call_get(call, state5_.d); break;
+        }
+    }
+
+
+    bool call_commit(const EaxEffectProps &props)
+    {
+        return std::visit([&](auto &arg)
+        { return CommitterFor<decltype(arg)>{props_, al_effect_props_}.commit(arg); },
+        props);
+    }
+
+    bool commit(int eax_version)
+    {
+        changed_ |= version_ != eax_version;
+        if(!changed_) return false;
+
+        bool ret{version_ != eax_version};
+        version_ = eax_version;
+        changed_ = false;
+
+        switch(eax_version)
+        {
+        case 1:
+            state1_.i = state1_.d;
+            ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d);
+            break;
+        case 2:
+            state2_.i = state2_.d;
+            ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d);
+            break;
+        case 3:
+            state3_.i = state3_.d;
+            ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d);
+            break;
+        case 4:
+            state4_.i = state4_.d;
+            ret |= call_commit(state4_.d);
+            break;
+        case 5:
+            state5_.i = state5_.d;
+            ret |= call_commit(state5_.d);
+            break;
+        }
+        al_effect_type_ = EnumFromEaxEffectType(props_);
+        return ret;
+    }
+#undef EAXCALL
+}; // EaxEffect
+
+using EaxEffectUPtr = std::unique_ptr<EaxEffect>;
+
+#endif // !EAX_EFFECT_INCLUDED

+ 32 - 0
libs/openal-soft/al/eax/exception.cpp

@@ -0,0 +1,32 @@
+#include "config.h"
+
+#include "exception.h"
+
+#include <cassert>
+#include <string>
+
+
+EaxException::EaxException(std::string_view context, std::string_view message)
+    : std::runtime_error{make_message(context, message)}
+{
+}
+EaxException::~EaxException() = default;
+
+
+std::string EaxException::make_message(std::string_view context, std::string_view message)
+{
+    auto what = std::string{};
+    if(context.empty() && message.empty())
+        return what;
+
+    what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1);
+    if(!context.empty())
+    {
+        what += "[";
+        what += context;
+        what += "] ";
+    }
+    what += message;
+
+    return what;
+}

+ 18 - 0
libs/openal-soft/al/eax/exception.h

@@ -0,0 +1,18 @@
+#ifndef EAX_EXCEPTION_INCLUDED
+#define EAX_EXCEPTION_INCLUDED
+
+#include <stdexcept>
+#include <string>
+#include <string_view>
+
+
+class EaxException : public std::runtime_error {
+    static std::string make_message(std::string_view context, std::string_view message);
+
+public:
+    EaxException(std::string_view context, std::string_view message);
+    ~EaxException() override;
+}; // EaxException
+
+
+#endif // !EAX_EXCEPTION_INCLUDED

+ 2 - 2
libs/openal-soft/al/eax_fx_slot_index.cpp → libs/openal-soft/al/eax/fx_slot_index.cpp

@@ -1,8 +1,8 @@
 #include "config.h"
 
-#include "eax_fx_slot_index.h"
+#include "fx_slot_index.h"
 
-#include "eax_exception.h"
+#include "exception.h"
 
 
 namespace

+ 4 - 5
libs/openal-soft/al/eax_fx_slot_index.h → libs/openal-soft/al/eax/fx_slot_index.h

@@ -3,17 +3,16 @@
 
 
 #include <cstddef>
+#include <optional>
 
-#include "aloptional.h"
-#include "eax_api.h"
+#include "api.h"
 
 
 using EaxFxSlotIndexValue = std::size_t;
 
-class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
-{
+class EaxFxSlotIndex : public std::optional<EaxFxSlotIndexValue> {
 public:
-    using al::optional<EaxFxSlotIndexValue>::optional;
+    using std::optional<EaxFxSlotIndexValue>::optional;
 
     EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
     EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }

+ 5 - 14
libs/openal-soft/al/eax_fx_slots.cpp → libs/openal-soft/al/eax/fx_slots.cpp

@@ -1,12 +1,11 @@
 #include "config.h"
 
-#include "eax_fx_slots.h"
+#include "fx_slots.h"
 
 #include <array>
 
-#include "eax_exception.h"
-
-#include "eax_api.h"
+#include "api.h"
+#include "exception.h"
 
 
 namespace
@@ -29,8 +28,7 @@ public:
 } // namespace
 
 
-void EaxFxSlots::initialize(
-    ALCcontext& al_context)
+void EaxFxSlots::initialize(ALCcontext& al_context)
 {
     initialize_fx_slots(al_context);
 }
@@ -57,12 +55,6 @@ ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index)
     return *fx_slots_[index.value()];
 }
 
-void EaxFxSlots::unlock_legacy() noexcept
-{
-    fx_slots_[0]->eax_unlock_legacy();
-    fx_slots_[1]->eax_unlock_legacy();
-}
-
 [[noreturn]]
 void EaxFxSlots::fail(
     const char* message)
@@ -70,8 +62,7 @@ void EaxFxSlots::fail(
     throw EaxFxSlotsException{message};
 }
 
-void EaxFxSlots::initialize_fx_slots(
-    ALCcontext& al_context)
+void EaxFxSlots::initialize_fx_slots(ALCcontext& al_context)
 {
     auto fx_slot_index = EaxFxSlotIndexValue{};
 

+ 8 - 16
libs/openal-soft/al/eax_fx_slots.h → libs/openal-soft/al/eax/fx_slots.h

@@ -6,16 +6,15 @@
 
 #include "al/auxeffectslot.h"
 
-#include "eax_api.h"
-
-#include "eax_fx_slot_index.h"
+#include "api.h"
+#include "call.h"
+#include "fx_slot_index.h"
 
 
 class EaxFxSlots
 {
 public:
-    void initialize(
-        ALCcontext& al_context);
+    void initialize(ALCcontext& al_context);
 
     void uninitialize() noexcept;
 
@@ -26,14 +25,9 @@ public:
     }
 
 
-    const ALeffectslot& get(
-        EaxFxSlotIndex index) const;
-
-    ALeffectslot& get(
-        EaxFxSlotIndex index);
-
-    void unlock_legacy() noexcept;
+    [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&;
 
+    [[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&;
 
 private:
     using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
@@ -43,11 +37,9 @@ private:
 
 
     [[noreturn]]
-    static void fail(
-        const char* message);
+    static void fail(const char* message);
 
-    void initialize_fx_slots(
-        ALCcontext& al_context);
+    void initialize_fx_slots(ALCcontext& al_context);
 }; // EaxFxSlots
 
 

+ 6 - 0
libs/openal-soft/al/eax/globals.h

@@ -0,0 +1,6 @@
+#ifndef EAX_GLOBALS_INCLUDED
+#define EAX_GLOBALS_INCLUDED
+
+inline bool eax_g_is_enabled{true};
+
+#endif /* EAX_GLOBALS_INCLUDED */

+ 26 - 0
libs/openal-soft/al/eax/utils.cpp

@@ -0,0 +1,26 @@
+#include "config.h"
+
+#include "utils.h"
+
+#include <cassert>
+#include <exception>
+
+#include "alstring.h"
+#include "core/logging.h"
+
+
+void eax_log_exception(std::string_view message) noexcept
+{
+    const auto exception_ptr = std::current_exception();
+    assert(exception_ptr);
+
+    try {
+        std::rethrow_exception(exception_ptr);
+    }
+    catch(const std::exception& ex) {
+        ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what());
+    }
+    catch(...) {
+        ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception.");
+    }
+}

+ 21 - 58
libs/openal-soft/al/eax_utils.h → libs/openal-soft/al/eax/utils.h

@@ -4,34 +4,26 @@
 #include <algorithm>
 #include <cstdint>
 #include <string>
+#include <string_view>
 #include <type_traits>
 
+#include "opthelpers.h"
 
-struct EaxAlLowPassParam
-{
+using EaxDirtyFlags = unsigned int;
+
+struct EaxAlLowPassParam {
     float gain;
     float gain_hf;
-}; // EaxAlLowPassParam
-
+};
 
-void eax_log_exception(
-    const char* message = nullptr) noexcept;
+void eax_log_exception(std::string_view message) noexcept;
 
-
-template<
-    typename TException,
-    typename TValue
->
-void eax_validate_range(
-    const char* value_name,
-    const TValue& value,
-    const TValue& min_value,
+template<typename TException, typename TValue>
+void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value,
     const TValue& max_value)
 {
-    if (value >= min_value && value <= max_value)
-    {
+    if(value >= min_value && value <= max_value) LIKELY
         return;
-    }
 
     const auto message =
         std::string{value_name} +
@@ -43,60 +35,38 @@ void eax_validate_range(
     throw TException{message.c_str()};
 }
 
+namespace detail {
 
-namespace detail
-{
-
-
-template<
-    typename T
->
-struct EaxIsBitFieldStruct
-{
+template<typename T>
+struct EaxIsBitFieldStruct {
 private:
     using yes = std::true_type;
     using no = std::false_type;
 
-    template<
-        typename U
-    >
+    template<typename U>
     static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
 
-    template<
-        typename
-    >
+    template<typename>
     static no test(...);
 
-
 public:
     static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
-}; // EaxIsBitFieldStruct
-
+};
 
-template<
-    typename T,
-    typename TValue
->
-inline bool eax_bit_fields_are_equal(
-    const T& lhs,
-    const T& rhs) noexcept
+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
+inline bool operator==(const T& lhs, const T& rhs) noexcept
 {
     using Value = std::conditional_t<
         sizeof(T) == 1,
@@ -107,13 +77,9 @@ inline bool operator==(
             std::conditional_t<
                 sizeof(T) == 4,
                 std::uint32_t,
-                void
-            >
-        >
-    >;
+                void>>>;
 
     static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
-
     return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
 }
 
@@ -121,12 +87,9 @@ template<
     typename T,
     std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
 >
-inline bool operator!=(
-    const T& lhs,
-    const T& rhs) noexcept
+inline bool operator!=(const T& lhs, const T& rhs) noexcept
 {
     return !(lhs == rhs);
 }
 
-
 #endif // !EAX_UTILS_INCLUDED

+ 7 - 10
libs/openal-soft/al/eax_x_ram.h → libs/openal-soft/al/eax/x_ram.h

@@ -24,15 +24,12 @@ constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
 constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
 constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
 
-
-ALboolean AL_APIENTRY EAXSetBufferMode(
-    ALsizei n,
-    const ALuint* buffers,
-    ALint value);
-
-ALenum AL_APIENTRY EAXGetBufferMode(
-    ALuint buffer,
-    ALint* pReserved);
-
+/* 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

+ 0 - 324
libs/openal-soft/al/eax_eax_call.cpp

@@ -1,324 +0,0 @@
-#include "config.h"
-
-#include "al/eax_eax_call.h"
-
-#include "al/eax_exception.h"
-
-
-namespace {
-
-constexpr auto deferred_flag = 0x80000000U;
-
-class EaxEaxCallException :
-    public EaxException
-{
-public:
-    explicit EaxEaxCallException(
-        const char* message)
-        :
-        EaxException{"EAX_EAX_CALL", message}
-    {
-    }
-}; // EaxEaxCallException
-
-} // namespace
-
-
-EaxEaxCall::EaxEaxCall(
-    bool is_get,
-    const GUID& property_set_guid,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_buffer,
-    ALuint property_size)
-    : is_get_{is_get}, version_{0}, property_set_id_{EaxEaxCallPropertySetId::none}
-    , property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id}
-    , property_buffer_{property_buffer}, property_size_{property_size}
-{
-    if (false)
-    {
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_Context)
-    {
-        version_ = 4;
-        property_set_id_ = EaxEaxCallPropertySetId::context;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_Context)
-    {
-        version_ = 5;
-        property_set_id_ = EaxEaxCallPropertySetId::context;
-    }
-    else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties)
-    {
-        version_ = 2;
-        fx_slot_index_ = 0u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
-        property_id_ = convert_eax_v2_0_listener_property_id(property_id_);
-    }
-    else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties)
-    {
-        version_ = 3;
-        fx_slot_index_ = 0u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0)
-    {
-        version_ = 4;
-        fx_slot_index_ = 0u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0)
-    {
-        version_ = 5;
-        fx_slot_index_ = 0u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1)
-    {
-        version_ = 4;
-        fx_slot_index_ = 1u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1)
-    {
-        version_ = 5;
-        fx_slot_index_ = 1u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2)
-    {
-        version_ = 4;
-        fx_slot_index_ = 2u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2)
-    {
-        version_ = 5;
-        fx_slot_index_ = 2u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3)
-    {
-        version_ = 4;
-        fx_slot_index_ = 3u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3)
-    {
-        version_ = 5;
-        fx_slot_index_ = 3u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot;
-    }
-    else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties)
-    {
-        version_ = 2;
-        property_set_id_ = EaxEaxCallPropertySetId::source;
-        property_id_ = convert_eax_v2_0_buffer_property_id(property_id_);
-    }
-    else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties)
-    {
-        version_ = 3;
-        property_set_id_ = EaxEaxCallPropertySetId::source;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX40_Source)
-    {
-        version_ = 4;
-        property_set_id_ = EaxEaxCallPropertySetId::source;
-    }
-    else if (property_set_guid == EAXPROPERTYID_EAX50_Source)
-    {
-        version_ = 5;
-        property_set_id_ = EaxEaxCallPropertySetId::source;
-    }
-    else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties)
-    {
-        version_ = 1;
-        fx_slot_index_ = 0u;
-        property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
-    }
-    else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties)
-    {
-        version_ = 1;
-        property_set_id_ = EaxEaxCallPropertySetId::source;
-    }
-    else
-    {
-        fail("Unsupported property set id.");
-    }
-
-    if (version_ < 1 || version_ > 5)
-    {
-        fail("EAX version out of range.");
-    }
-
-    if(!(property_id&deferred_flag))
-    {
-        if(property_set_id_ != EaxEaxCallPropertySetId::fx_slot && property_id_ != 0)
-        {
-            if (!property_buffer)
-            {
-                fail("Null property buffer.");
-            }
-
-            if (property_size == 0)
-            {
-                fail("Empty property.");
-            }
-        }
-    }
-
-    if(property_set_id_ == EaxEaxCallPropertySetId::source && property_source_id_ == 0)
-    {
-        fail("Null AL source id.");
-    }
-
-    if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot)
-    {
-        if (property_id_ < EAXFXSLOT_NONE)
-        {
-            property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect;
-        }
-    }
-}
-
-[[noreturn]]
-void EaxEaxCall::fail(
-    const char* message)
-{
-    throw EaxEaxCallException{message};
-}
-
-ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id(
-    ALuint property_id)
-{
-    switch (property_id)
-    {
-        case DSPROPERTY_EAX20LISTENER_NONE:
-            return EAXREVERB_NONE;
-
-        case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS:
-            return EAXREVERB_ALLPARAMETERS;
-
-        case DSPROPERTY_EAX20LISTENER_ROOM:
-            return EAXREVERB_ROOM;
-
-        case DSPROPERTY_EAX20LISTENER_ROOMHF:
-            return EAXREVERB_ROOMHF;
-
-        case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR:
-            return EAXREVERB_ROOMROLLOFFFACTOR;
-
-        case DSPROPERTY_EAX20LISTENER_DECAYTIME:
-            return EAXREVERB_DECAYTIME;
-
-        case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO:
-            return EAXREVERB_DECAYHFRATIO;
-
-        case DSPROPERTY_EAX20LISTENER_REFLECTIONS:
-            return EAXREVERB_REFLECTIONS;
-
-        case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY:
-            return EAXREVERB_REFLECTIONSDELAY;
-
-        case DSPROPERTY_EAX20LISTENER_REVERB:
-            return EAXREVERB_REVERB;
-
-        case DSPROPERTY_EAX20LISTENER_REVERBDELAY:
-            return EAXREVERB_REVERBDELAY;
-
-        case DSPROPERTY_EAX20LISTENER_ENVIRONMENT:
-            return EAXREVERB_ENVIRONMENT;
-
-        case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE:
-            return EAXREVERB_ENVIRONMENTSIZE;
-
-        case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION:
-            return EAXREVERB_ENVIRONMENTDIFFUSION;
-
-        case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF:
-            return EAXREVERB_AIRABSORPTIONHF;
-
-        case DSPROPERTY_EAX20LISTENER_FLAGS:
-            return EAXREVERB_FLAGS;
-
-        default:
-            fail("Unsupported EAX 2.0 listener property id.");
-    }
-}
-
-ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id(
-    ALuint property_id)
-{
-    switch (property_id)
-    {
-        case DSPROPERTY_EAX20BUFFER_NONE:
-            return EAXSOURCE_NONE;
-
-        case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS:
-            return EAXSOURCE_ALLPARAMETERS;
-
-        case DSPROPERTY_EAX20BUFFER_DIRECT:
-            return EAXSOURCE_DIRECT;
-
-        case DSPROPERTY_EAX20BUFFER_DIRECTHF:
-            return EAXSOURCE_DIRECTHF;
-
-        case DSPROPERTY_EAX20BUFFER_ROOM:
-            return EAXSOURCE_ROOM;
-
-        case DSPROPERTY_EAX20BUFFER_ROOMHF:
-            return EAXSOURCE_ROOMHF;
-
-        case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR:
-            return EAXSOURCE_ROOMROLLOFFFACTOR;
-
-        case DSPROPERTY_EAX20BUFFER_OBSTRUCTION:
-            return EAXSOURCE_OBSTRUCTION;
-
-        case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO:
-            return EAXSOURCE_OBSTRUCTIONLFRATIO;
-
-        case DSPROPERTY_EAX20BUFFER_OCCLUSION:
-            return EAXSOURCE_OCCLUSION;
-
-        case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO:
-            return EAXSOURCE_OCCLUSIONLFRATIO;
-
-        case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO:
-            return EAXSOURCE_OCCLUSIONROOMRATIO;
-
-        case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF:
-            return EAXSOURCE_OUTSIDEVOLUMEHF;
-
-        case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR:
-            return EAXSOURCE_AIRABSORPTIONFACTOR;
-
-        case DSPROPERTY_EAX20BUFFER_FLAGS:
-            return EAXSOURCE_FLAGS;
-
-        default:
-            fail("Unsupported EAX 2.0 buffer property id.");
-    }
-}
-
-
-EaxEaxCall create_eax_call(
-    bool is_get,
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_buffer,
-    ALuint property_size)
-{
-    if(!property_set_id)
-        throw EaxEaxCallException{"Null property set ID."};
-
-    return EaxEaxCall{
-        is_get,
-        *property_set_id,
-        property_id,
-        property_source_id,
-        property_buffer,
-        property_size
-    };
-}

+ 0 - 117
libs/openal-soft/al/eax_eax_call.h

@@ -1,117 +0,0 @@
-#ifndef EAX_EAX_CALL_INCLUDED
-#define EAX_EAX_CALL_INCLUDED
-
-
-#include "AL/al.h"
-
-#include "alspan.h"
-
-#include "eax_api.h"
-#include "eax_fx_slot_index.h"
-
-
-enum class EaxEaxCallPropertySetId
-{
-    none,
-
-    context,
-    fx_slot,
-    source,
-    fx_slot_effect,
-}; // EaxEaxCallPropertySetId
-
-
-class EaxEaxCall
-{
-public:
-    EaxEaxCall(
-        bool is_get,
-        const GUID& property_set_guid,
-        ALuint property_id,
-        ALuint property_source_id,
-        ALvoid* property_buffer,
-        ALuint property_size);
-
-    bool is_get() const noexcept { return is_get_; }
-    int get_version() const noexcept { return version_; }
-    EaxEaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; }
-    ALuint get_property_id() const noexcept { return property_id_; }
-    ALuint get_property_al_name() const noexcept { return property_source_id_; }
-    EaxFxSlotIndex get_fx_slot_index() const noexcept { return fx_slot_index_; }
-
-    template<
-        typename TException,
-        typename TValue
-    >
-    TValue& get_value() const
-    {
-        if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
-        {
-            throw TException{"Property buffer too small."};
-        }
-
-        return *static_cast<TValue*>(property_buffer_);
-    }
-
-    template<
-        typename TException,
-        typename TValue
-    >
-    al::span<TValue> get_values() const
-    {
-        if (property_size_ < static_cast<ALuint>(sizeof(TValue)))
-        {
-            throw TException{"Property buffer too small."};
-        }
-
-        const auto count = property_size_ / sizeof(TValue);
-
-        return al::span<TValue>{static_cast<TValue*>(property_buffer_), count};
-    }
-
-    template<
-        typename TException,
-        typename TValue
-    >
-    void set_value(
-        const TValue& value) const
-    {
-        get_value<TException, TValue>() = value;
-    }
-
-
-private:
-    const bool is_get_;
-    int version_;
-    EaxFxSlotIndex fx_slot_index_;
-    EaxEaxCallPropertySetId property_set_id_;
-
-    ALuint property_id_;
-    const ALuint property_source_id_;
-    ALvoid*const property_buffer_;
-    const ALuint property_size_;
-
-
-    [[noreturn]]
-    static void fail(
-        const char* message);
-
-
-    static ALuint convert_eax_v2_0_listener_property_id(
-        ALuint property_id);
-
-    static ALuint convert_eax_v2_0_buffer_property_id(
-        ALuint property_id);
-}; // EaxEaxCall
-
-
-EaxEaxCall create_eax_call(
-    bool is_get,
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_buffer,
-    ALuint property_size);
-
-
-#endif // !EAX_EAX_CALL_INCLUDED

+ 0 - 3
libs/openal-soft/al/eax_effect.cpp

@@ -1,3 +0,0 @@
-#include "config.h"
-
-#include "eax_effect.h"

+ 0 - 44
libs/openal-soft/al/eax_effect.h

@@ -1,44 +0,0 @@
-#ifndef EAX_EFFECT_INCLUDED
-#define EAX_EFFECT_INCLUDED
-
-
-#include <memory>
-
-#include "AL/al.h"
-#include "core/effects/base.h"
-#include "eax_eax_call.h"
-
-class EaxEffect
-{
-public:
-    EaxEffect(ALenum type) : al_effect_type_{type} { }
-    virtual ~EaxEffect() = default;
-
-    const ALenum al_effect_type_;
-    EffectProps al_effect_props_{};
-
-    virtual void dispatch(const EaxEaxCall& eax_call) = 0;
-
-    // Returns "true" if any immediated property was changed.
-    // [[nodiscard]]
-    virtual bool apply_deferred() = 0;
-}; // EaxEffect
-
-
-using EaxEffectUPtr = std::unique_ptr<EaxEffect>;
-
-EaxEffectUPtr eax_create_eax_null_effect();
-EaxEffectUPtr eax_create_eax_chorus_effect();
-EaxEffectUPtr eax_create_eax_distortion_effect();
-EaxEffectUPtr eax_create_eax_echo_effect();
-EaxEffectUPtr eax_create_eax_flanger_effect();
-EaxEffectUPtr eax_create_eax_frequency_shifter_effect();
-EaxEffectUPtr eax_create_eax_vocal_morpher_effect();
-EaxEffectUPtr eax_create_eax_pitch_shifter_effect();
-EaxEffectUPtr eax_create_eax_ring_modulator_effect();
-EaxEffectUPtr eax_create_eax_auto_wah_effect();
-EaxEffectUPtr eax_create_eax_compressor_effect();
-EaxEffectUPtr eax_create_eax_equalizer_effect();
-EaxEffectUPtr eax_create_eax_reverb_effect();
-
-#endif // !EAX_EFFECT_INCLUDED

+ 0 - 63
libs/openal-soft/al/eax_exception.cpp

@@ -1,63 +0,0 @@
-#include "config.h"
-
-#include "eax_exception.h"
-
-#include <cassert>
-
-#include <string>
-
-
-EaxException::EaxException(
-    const char* context,
-    const char* message)
-    :
-    std::runtime_error{make_message(context, message)}
-{
-}
-
-std::string EaxException::make_message(
-    const char* context,
-    const char* message)
-{
-    const auto context_size = (context ? std::string::traits_type::length(context) : 0);
-    const auto has_contex = (context_size > 0);
-
-    const auto message_size = (message ? std::string::traits_type::length(message) : 0);
-    const auto has_message = (message_size > 0);
-
-    if (!has_contex && !has_message)
-    {
-        return std::string{};
-    }
-
-    static constexpr char left_prefix[] = "[";
-    const auto left_prefix_size = std::string::traits_type::length(left_prefix);
-
-    static constexpr char right_prefix[] = "] ";
-    const auto right_prefix_size = std::string::traits_type::length(right_prefix);
-
-    const auto what_size =
-        (
-            has_contex ?
-            left_prefix_size + context_size + right_prefix_size :
-            0) +
-        message_size +
-        1;
-
-    auto what = std::string{};
-    what.reserve(what_size);
-
-    if (has_contex)
-    {
-        what.append(left_prefix, left_prefix_size);
-        what.append(context, context_size);
-        what.append(right_prefix, right_prefix_size);
-    }
-
-    if (has_message)
-    {
-        what.append(message, message_size);
-    }
-
-    return what;
-}

+ 0 - 25
libs/openal-soft/al/eax_exception.h

@@ -1,25 +0,0 @@
-#ifndef EAX_EXCEPTION_INCLUDED
-#define EAX_EXCEPTION_INCLUDED
-
-
-#include <stdexcept>
-#include <string>
-
-
-class EaxException :
-    public std::runtime_error
-{
-public:
-    EaxException(
-        const char* context,
-        const char* message);
-
-
-private:
-    static std::string make_message(
-        const char* context,
-        const char* message);
-}; // EaxException
-
-
-#endif // !EAX_EXCEPTION_INCLUDED

+ 0 - 21
libs/openal-soft/al/eax_globals.cpp

@@ -1,21 +0,0 @@
-#include "config.h"
-
-#include "eax_globals.h"
-
-
-bool eax_g_is_enabled = true;
-
-
-const char eax1_ext_name[] = "EAX";
-const char eax2_ext_name[] = "EAX2.0";
-const char eax3_ext_name[] = "EAX3.0";
-const char eax4_ext_name[] = "EAX4.0";
-const char eax5_ext_name[] = "EAX5.0";
-
-const char eax_x_ram_ext_name[] = "EAX-RAM";
-
-const char eax_eax_set_func_name[] = "EAXSet";
-const char eax_eax_get_func_name[] = "EAXGet";
-
-const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode";
-const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode";

+ 0 - 22
libs/openal-soft/al/eax_globals.h

@@ -1,22 +0,0 @@
-#ifndef EAX_GLOBALS_INCLUDED
-#define EAX_GLOBALS_INCLUDED
-
-
-extern bool eax_g_is_enabled;
-
-
-extern const char eax1_ext_name[];
-extern const char eax2_ext_name[];
-extern const char eax3_ext_name[];
-extern const char eax4_ext_name[];
-extern const char eax5_ext_name[];
-
-extern const char eax_x_ram_ext_name[];
-
-extern const char eax_eax_set_func_name[];
-extern const char eax_eax_get_func_name[];
-
-extern const char eax_eax_set_buffer_mode_func_name[];
-extern const char eax_eax_get_buffer_mode_func_name[];
-
-#endif // !EAX_GLOBALS_INCLUDED

+ 0 - 36
libs/openal-soft/al/eax_utils.cpp

@@ -1,36 +0,0 @@
-#include "config.h"
-
-#include "eax_utils.h"
-
-#include <cassert>
-#include <exception>
-
-#include "core/logging.h"
-
-
-void eax_log_exception(
-    const char* message) noexcept
-{
-    const auto exception_ptr = std::current_exception();
-
-    assert(exception_ptr);
-
-    if (message)
-    {
-        ERR("%s\n", message);
-    }
-
-    try
-    {
-        std::rethrow_exception(exception_ptr);
-    }
-    catch (const std::exception& ex)
-    {
-        const auto ex_message = ex.what();
-        ERR("%s\n", ex_message);
-    }
-    catch (...)
-    {
-        ERR("%s\n", "Generic exception.");
-    }
-}

+ 0 - 3
libs/openal-soft/al/eax_x_ram.cpp

@@ -1,3 +0,0 @@
-#include "config.h"
-
-#include "eax_x_ram.h"

+ 350 - 374
libs/openal-soft/al/effect.cpp

@@ -28,9 +28,13 @@
 #include <iterator>
 #include <memory>
 #include <mutex>
-#include <new>
 #include <numeric>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
 #include <utility>
+#include <variant>
+#include <vector>
 
 #include "AL/al.h"
 #include "AL/alc.h"
@@ -38,26 +42,23 @@
 #include "AL/efx-presets.h"
 #include "AL/efx.h"
 
+#include "al/effects/effects.h"
 #include "albit.h"
 #include "alc/context.h"
 #include "alc/device.h"
-#include "alc/effects/base.h"
 #include "alc/inprogext.h"
 #include "almalloc.h"
 #include "alnumeric.h"
+#include "alspan.h"
 #include "alstring.h"
-#include "core/except.h"
 #include "core/logging.h"
+#include "direct_defs.h"
+#include "error.h"
+#include "intrusive_ptr.h"
 #include "opthelpers.h"
-#include "vector.h"
 
-#ifdef ALSOFT_EAX
-#include <cassert>
 
-#include "eax_exception.h"
-#endif // ALSOFT_EAX
-
-const EffectList gEffectList[16]{
+const std::array<EffectList,16> gEffectList{{
     { "eaxreverb",   EAXREVERB_EFFECT,   AL_EFFECT_EAXREVERB },
     { "reverb",      REVERB_EFFECT,      AL_EFFECT_REVERB },
     { "autowah",     AUTOWAH_EFFECT,     AL_EFFECT_AUTOWAH },
@@ -73,117 +74,69 @@ const EffectList gEffectList[16]{
     { "vmorpher",    VMORPHER_EFFECT,    AL_EFFECT_VOCAL_MORPHER },
     { "dedicated",   DEDICATED_EFFECT,   AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
     { "dedicated",   DEDICATED_EFFECT,   AL_EFFECT_DEDICATED_DIALOGUE },
-    { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT },
-};
-
-bool DisabledEffects[MAX_EFFECTS];
+    { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_SOFT },
+}};
 
 
-effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
-{
-    std::va_list args;
-    va_start(args, msg);
-    setMessage(msg, args);
-    va_end(args);
-}
-
 namespace {
 
-struct EffectPropsItem {
-    ALenum Type;
-    const EffectProps &DefaultProps;
-    const EffectVtable &Vtable;
-};
-constexpr EffectPropsItem EffectPropsList[] = {
-    { AL_EFFECT_NULL, NullEffectProps, NullEffectVtable },
-    { AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable },
-    { AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable },
-    { AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable },
-    { AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable },
-    { AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable },
-    { AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable },
-    { AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable },
-    { AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable },
-    { AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable },
-    { AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable },
-    { AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable },
-    { AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable },
-    { AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable },
-    { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable },
-    { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable },
-    { AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable },
-};
-
+using SubListAllocator = al::allocator<std::array<ALeffect,64>>;
 
-void ALeffect_setParami(ALeffect *effect, ALenum param, int value)
-{ effect->vtab->setParami(&effect->Props, param, value); }
-void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values)
-{ effect->vtab->setParamiv(&effect->Props, param, values); }
-void ALeffect_setParamf(ALeffect *effect, ALenum param, float value)
-{ effect->vtab->setParamf(&effect->Props, param, value); }
-void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values)
-{ effect->vtab->setParamfv(&effect->Props, param, values); }
-
-void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value)
-{ effect->vtab->getParami(&effect->Props, param, value); }
-void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values)
-{ effect->vtab->getParamiv(&effect->Props, param, values); }
-void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value)
-{ effect->vtab->getParamf(&effect->Props, param, value); }
-void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values)
-{ effect->vtab->getParamfv(&effect->Props, param, values); }
-
-
-const EffectPropsItem *getEffectPropsItemByType(ALenum type)
+constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps&
 {
-    auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList),
-        [type](const EffectPropsItem &item) noexcept -> bool
-        { return item.Type == type; });
-    return (iter != std::end(EffectPropsList)) ? std::addressof(*iter) : nullptr;
+    switch(type)
+    {
+    case AL_EFFECT_NULL: return NullEffectProps;
+    case AL_EFFECT_EAXREVERB: return ReverbEffectProps;
+    case AL_EFFECT_REVERB: return StdReverbEffectProps;
+    case AL_EFFECT_AUTOWAH: return AutowahEffectProps;
+    case AL_EFFECT_CHORUS: return ChorusEffectProps;
+    case AL_EFFECT_COMPRESSOR: return CompressorEffectProps;
+    case AL_EFFECT_DISTORTION: return DistortionEffectProps;
+    case AL_EFFECT_ECHO: return EchoEffectProps;
+    case AL_EFFECT_EQUALIZER: return EqualizerEffectProps;
+    case AL_EFFECT_FLANGER: return FlangerEffectProps;
+    case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps;
+    case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps;
+    case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps;
+    case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps;
+    case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps;
+    case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps;
+    case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps;
+    }
+    return NullEffectProps;
 }
 
-void InitEffectParams(ALeffect *effect, ALenum type)
+void InitEffectParams(ALeffect *effect, ALenum type) noexcept
 {
-    const EffectPropsItem *item{getEffectPropsItemByType(type)};
-    if(item)
-    {
-        effect->Props = item->DefaultProps;
-        effect->vtab = &item->Vtable;
-    }
-    else
-    {
-        effect->Props = EffectProps{};
-        effect->vtab = &NullEffectVtable;
-    }
+    effect->Props = GetDefaultProps(type);
     effect->type = type;
 }
 
-bool EnsureEffects(ALCdevice *device, size_t needed)
-{
-    size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0},
+auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool
+try {
+    size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz,
         [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
         { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
 
     while(needed > count)
     {
-        if UNLIKELY(device->EffectList.size() >= 1<<25)
+        if(device->EffectList.size() >= 1<<25) UNLIKELY
             return false;
 
-        device->EffectList.emplace_back();
-        auto sublist = device->EffectList.end() - 1;
-        sublist->FreeMask = ~0_u64;
-        sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
-        if UNLIKELY(!sublist->Effects)
-        {
-            device->EffectList.pop_back();
-            return false;
-        }
-        count += 64;
+        EffectSubList sublist{};
+        sublist.FreeMask = ~0_u64;
+        sublist.Effects = SubListAllocator{}.allocate(1);
+        device->EffectList.emplace_back(std::move(sublist));
+        count += std::tuple_size_v<SubListAllocator::value_type>;
     }
     return true;
 }
+catch(...) {
+    return false;
+}
 
-ALeffect *AllocEffect(ALCdevice *device)
+ALeffect *AllocEffect(ALCdevice *device) noexcept
 {
     auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
         [](const EffectSubList &entry) noexcept -> bool
@@ -192,7 +145,7 @@ ALeffect *AllocEffect(ALCdevice *device)
     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
     ASSUME(slidx < 64);
 
-    ALeffect *effect{al::construct_at(sublist->Effects + slidx)};
+    ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))};
     InitEffectParams(effect, AL_EFFECT_NULL);
 
     /* Add 1 to avoid effect ID 0. */
@@ -205,339 +158,347 @@ ALeffect *AllocEffect(ALCdevice *device)
 
 void FreeEffect(ALCdevice *device, ALeffect *effect)
 {
+    device->mEffectNames.erase(effect->id);
+
     const ALuint id{effect->id - 1};
     const size_t lidx{id >> 6};
     const ALuint slidx{id & 0x3f};
 
-    al::destroy_at(effect);
+    std::destroy_at(effect);
 
     device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
 }
 
-inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
+inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect*
 {
     const size_t lidx{(id-1) >> 6};
     const ALuint slidx{(id-1) & 0x3f};
 
-    if UNLIKELY(lidx >= device->EffectList.size())
+    if(lidx >= device->EffectList.size()) UNLIKELY
         return nullptr;
     EffectSubList &sublist = device->EffectList[lidx];
-    if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+    if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
         return nullptr;
-    return sublist.Effects + slidx;
+    return al::to_address(sublist.Effects->begin() + slidx);
 }
 
 } // namespace
 
-AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
-    if UNLIKELY(n < 0)
-        context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
-    if UNLIKELY(n <= 0) return;
+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};
+    if(n <= 0) UNLIKELY return;
 
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->EffectLock};
-    if(!EnsureEffects(device, static_cast<ALuint>(n)))
-    {
-        context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
-        return;
-    }
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
-    if LIKELY(n == 1)
-    {
-        /* Special handling for the easy and normal case. */
-        ALeffect *effect{AllocEffect(device)};
-        effects[0] = effect->id;
-    }
-    else
-    {
-        /* Store the allocated buffer IDs in a separate local list, to avoid
-         * modifying the user storage in case of failure.
-         */
-        al::vector<ALuint> ids;
-        ids.reserve(static_cast<ALuint>(n));
-        do {
-            ALeffect *effect{AllocEffect(device)};
-            ids.emplace_back(effect->id);
-        } while(--n);
-        std::copy(ids.cbegin(), ids.cend(), effects);
-    }
-}
-END_API_FUNC
+    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"};
 
-AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+    std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; });
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
 
-    if UNLIKELY(n < 0)
-        context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
-    if UNLIKELY(n <= 0) return;
+AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects)
+FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n,
+    const ALuint *effects) noexcept
+try {
+    if(n < 0)
+        throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n};
+    if(n <= 0) UNLIKELY return;
 
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     /* First try to find any effects that are invalid. */
     auto validate_effect = [device](const ALuint eid) -> bool
     { return !eid || LookupEffect(device, eid) != nullptr; };
 
-    const ALuint *effects_end = effects + n;
-    auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
-    if UNLIKELY(inveffect != effects_end)
-    {
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
-        return;
-    }
+    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};
 
     /* All good. Delete non-0 effect IDs. */
     auto delete_effect = [device](ALuint eid) -> void
     {
-        ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
-        if(effect) FreeEffect(device, effect);
+        if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr})
+            FreeEffect(device, effect);
     };
-    std::for_each(effects, effects_end, delete_effect);
+    std::for_each(eids.begin(), eids.end(), delete_effect);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
-START_API_FUNC
+AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect)
+FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept
 {
-    ContextRef context{GetContextRef()};
-    if LIKELY(context)
-    {
-        ALCdevice *device{context->mALDevice.get()};
-        std::lock_guard<std::mutex> _{device->EffectLock};
-        if(!effect || LookupEffect(device, effect))
-            return AL_TRUE;
-    }
+    ALCdevice *device{context->mALDevice.get()};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
+    if(!effect || LookupEffect(device, effect))
+        return AL_TRUE;
     return AL_FALSE;
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else if(param == AL_EFFECT_TYPE)
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    switch(param)
     {
-        bool isOk{value == AL_EFFECT_NULL};
-        if(!isOk)
+    case AL_EFFECT_TYPE:
+        if(value != AL_EFFECT_NULL)
         {
-            for(const EffectList &effectitem : gEffectList)
-            {
-                if(value == effectitem.val && !DisabledEffects[effectitem.type])
-                {
-                    isOk = true;
-                    break;
-                }
-            }
+            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};
         }
 
-        if(isOk)
-            InitEffectParams(aleffect, value);
-        else
-            context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
+        InitEffectParams(aleffect, value);
+        return;
     }
-    else try
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,value](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_setParami(aleffect, param, value);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
-START_API_FUNC
-{
+AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param,
+    const ALint *values) noexcept
+try {
     switch(param)
     {
     case AL_EFFECT_TYPE:
-        alEffecti(effect, param, values[0]);
+        alEffectiDirect(context, effect, param, *values);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,values](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_setParamiv(aleffect, param, values);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect) UNLIKELY
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,value](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_setParamf(aleffect, param, value);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,values](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_setParamfv(aleffect, param, values);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else if(param == AL_EFFECT_TYPE)
-        *value = aleffect->type;
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    switch(param)
     {
-        /* Call the appropriate handler */
-        ALeffect_getParami(aleffect, param, value);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
+    case AL_EFFECT_TYPE:
+        *value = aleffect->type;
+        return;
     }
+
+    /* Call the appropriate handler */
+    std::visit([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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
-START_API_FUNC
-{
+AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param,
+    ALint *values) noexcept
+try {
     switch(param)
     {
     case AL_EFFECT_TYPE:
-        alGetEffecti(effect, param, values);
+        alGetEffectiDirect(context, effect, param, values);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,values](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_getParamiv(aleffect, param, values);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,value](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_getParamf(aleffect, param, value);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->EffectLock};
+    std::lock_guard<std::mutex> effectlock{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
-    if UNLIKELY(!aleffect)
-        context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
-    else try
+    if(!aleffect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect};
+
+    /* Call the appropriate handler */
+    std::visit([aleffect,param,values](auto &arg)
     {
-        /* Call the appropriate handler */
-        ALeffect_getParamfv(aleffect, param, values);
-    }
-    catch(effect_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+        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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
 
 void InitEffect(ALeffect *effect)
@@ -545,26 +506,43 @@ void InitEffect(ALeffect *effect)
     InitEffectParams(effect, AL_EFFECT_NULL);
 }
 
+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 effect = LookupEffect(device, id);
+    if(!effect)
+        throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id};
+
+    device->mEffectNames.insert_or_assign(id, name);
+}
+
+
 EffectSubList::~EffectSubList()
 {
+    if(!Effects)
+        return;
+
     uint64_t usemask{~FreeMask};
     while(usemask)
     {
         const int idx{al::countr_zero(usemask)};
-        al::destroy_at(Effects+idx);
+        std::destroy_at(al::to_address(Effects->begin()+idx));
         usemask &= ~(1_u64 << idx);
     }
     FreeMask = ~usemask;
-    al_free(Effects);
+    SubListAllocator{}.deallocate(Effects, 1);
     Effects = nullptr;
 }
 
 
-#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
-static const struct {
-    const char name[32];
+struct EffectPreset {
+    const char name[32]; /* NOLINT(*-avoid-c-arrays) */
     EFXEAXREVERBPROPERTIES props;
-} reverblist[] = {
+};
+#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x}
+static constexpr std::array reverblist{
     DECL(GENERIC),
     DECL(PADDEDCELL),
     DECL(ROOM),
@@ -694,61 +672,62 @@ static const struct {
 };
 #undef DECL
 
-void LoadReverbPreset(const char *name, ALeffect *effect)
+void LoadReverbPreset(const std::string_view name, ALeffect *effect)
 {
-    if(al::strcasecmp(name, "NONE") == 0)
+    using namespace std::string_view_literals;
+
+    if(al::case_compare(name, "NONE"sv) == 0)
     {
         InitEffectParams(effect, AL_EFFECT_NULL);
         TRACE("Loading reverb '%s'\n", "NONE");
         return;
     }
 
-    if(!DisabledEffects[EAXREVERB_EFFECT])
+    if(!DisabledEffects.test(EAXREVERB_EFFECT))
         InitEffectParams(effect, AL_EFFECT_EAXREVERB);
-    else if(!DisabledEffects[REVERB_EFFECT])
+    else if(!DisabledEffects.test(REVERB_EFFECT))
         InitEffectParams(effect, AL_EFFECT_REVERB);
     else
         InitEffectParams(effect, AL_EFFECT_NULL);
     for(const auto &reverbitem : reverblist)
     {
-        const EFXEAXREVERBPROPERTIES *props;
-
-        if(al::strcasecmp(name, reverbitem.name) != 0)
+        if(al::case_compare(name, std::data(reverbitem.name)) != 0)
             continue;
 
-        TRACE("Loading reverb '%s'\n", reverbitem.name);
-        props = &reverbitem.props;
-        effect->Props.Reverb.Density   = props->flDensity;
-        effect->Props.Reverb.Diffusion = props->flDiffusion;
-        effect->Props.Reverb.Gain   = props->flGain;
-        effect->Props.Reverb.GainHF = props->flGainHF;
-        effect->Props.Reverb.GainLF = props->flGainLF;
-        effect->Props.Reverb.DecayTime    = props->flDecayTime;
-        effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
-        effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
-        effect->Props.Reverb.ReflectionsGain   = props->flReflectionsGain;
-        effect->Props.Reverb.ReflectionsDelay  = props->flReflectionsDelay;
-        effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
-        effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
-        effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
-        effect->Props.Reverb.LateReverbGain   = props->flLateReverbGain;
-        effect->Props.Reverb.LateReverbDelay  = props->flLateReverbDelay;
-        effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
-        effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
-        effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
-        effect->Props.Reverb.EchoTime  = props->flEchoTime;
-        effect->Props.Reverb.EchoDepth = props->flEchoDepth;
-        effect->Props.Reverb.ModulationTime  = props->flModulationTime;
-        effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
-        effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
-        effect->Props.Reverb.HFReference = props->flHFReference;
-        effect->Props.Reverb.LFReference = props->flLFReference;
-        effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
-        effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
+        TRACE("Loading reverb '%s'\n", std::data(reverbitem.name));
+        const auto &props = reverbitem.props;
+        auto &dst = std::get<ReverbProps>(effect->Props);
+        dst.Density   = props.flDensity;
+        dst.Diffusion = props.flDiffusion;
+        dst.Gain   = props.flGain;
+        dst.GainHF = props.flGainHF;
+        dst.GainLF = props.flGainLF;
+        dst.DecayTime    = props.flDecayTime;
+        dst.DecayHFRatio = props.flDecayHFRatio;
+        dst.DecayLFRatio = props.flDecayLFRatio;
+        dst.ReflectionsGain   = props.flReflectionsGain;
+        dst.ReflectionsDelay  = props.flReflectionsDelay;
+        dst.ReflectionsPan[0] = props.flReflectionsPan[0];
+        dst.ReflectionsPan[1] = props.flReflectionsPan[1];
+        dst.ReflectionsPan[2] = props.flReflectionsPan[2];
+        dst.LateReverbGain   = props.flLateReverbGain;
+        dst.LateReverbDelay  = props.flLateReverbDelay;
+        dst.LateReverbPan[0] = props.flLateReverbPan[0];
+        dst.LateReverbPan[1] = props.flLateReverbPan[1];
+        dst.LateReverbPan[2] = props.flLateReverbPan[2];
+        dst.EchoTime  = props.flEchoTime;
+        dst.EchoDepth = props.flEchoDepth;
+        dst.ModulationTime  = props.flModulationTime;
+        dst.ModulationDepth = props.flModulationDepth;
+        dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF;
+        dst.HFReference = props.flHFReference;
+        dst.LFReference = props.flLFReference;
+        dst.RoomRolloffFactor = props.flRoomRolloffFactor;
+        dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE;
         return;
     }
 
-    WARN("Reverb preset '%s' not found\n", name);
+    WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data());
 }
 
 bool IsValidEffectType(ALenum type) noexcept
@@ -756,10 +735,7 @@ bool IsValidEffectType(ALenum type) noexcept
     if(type == AL_EFFECT_NULL)
         return true;
 
-    for(const auto &effect_item : gEffectList)
-    {
-        if(type == effect_item.val && !DisabledEffects[effect_item.type])
-            return true;
-    }
-    return false;
+    auto check_effect = [type](const EffectList &item) noexcept -> bool
+    { return type == item.val && !DisabledEffects.test(item.type); };
+    return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect);
 }

+ 33 - 12
libs/openal-soft/al/effect.h

@@ -1,11 +1,19 @@
 #ifndef AL_EFFECT_H
 #define AL_EFFECT_H
 
+#include <array>
+#include <bitset>
+#include <cstdint>
+#include <string_view>
+#include <utility>
+
 #include "AL/al.h"
+#include "AL/alc.h"
 #include "AL/efx.h"
 
-#include "al/effects/effects.h"
-#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "core/effects/base.h"
 
 
 enum {
@@ -27,16 +35,14 @@ enum {
 
     MAX_EFFECTS
 };
-extern bool DisabledEffects[MAX_EFFECTS];
-
-extern float ReverbBoost;
+inline std::bitset<MAX_EFFECTS> DisabledEffects;
 
 struct EffectList {
-    const char name[16];
-    int type;
+    const char name[16]; /* NOLINT(*-avoid-c-arrays) */
+    ALuint type;
     ALenum val;
 };
-extern const EffectList gEffectList[16];
+extern const std::array<EffectList,16> gEffectList;
 
 
 struct ALeffect {
@@ -45,18 +51,33 @@ struct ALeffect {
 
     EffectProps Props{};
 
-    const EffectVtable *vtab{nullptr};
-
     /* Self ID */
     ALuint id{0u};
 
-    DISABLE_ALLOC()
+    static void SetName(ALCcontext *context, ALuint id, std::string_view name);
+
+    DISABLE_ALLOC
 };
 
 void InitEffect(ALeffect *effect);
 
-void LoadReverbPreset(const char *name, ALeffect *effect);
+void LoadReverbPreset(const std::string_view name, ALeffect *effect);
 
 bool IsValidEffectType(ALenum type) noexcept;
 
+struct EffectSubList {
+    uint64_t FreeMask{~0_u64};
+    gsl::owner<std::array<ALeffect,64>*> Effects{nullptr}; /* 64 */
+
+    EffectSubList() noexcept = default;
+    EffectSubList(const EffectSubList&) = delete;
+    EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
+    { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
+    ~EffectSubList();
+
+    EffectSubList& operator=(const EffectSubList&) = delete;
+    EffectSubList& operator=(EffectSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
+};
+
 #endif

+ 138 - 446
libs/openal-soft/al/effects/autowah.cpp

@@ -13,536 +13,228 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Autowah_setParamf(EffectProps *props, ALenum param, float val)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    AutowahProps props{};
+    props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
+    props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
+    props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
+    props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
+    return props;
+}
+
+} // namespace
+
+const EffectProps AutowahEffectProps{genDefaultProps()};
+
+void 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 EffectHandler::SetParamf(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"};
-        props->Autowah.AttackTime = val;
+        props.AttackTime = val;
         break;
 
     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"};
-        props->Autowah.ReleaseTime = val;
+        props.ReleaseTime = val;
         break;
 
     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"};
-        props->Autowah.Resonance = val;
+        props.Resonance = val;
         break;
 
     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"};
-        props->Autowah.PeakGain = val;
+        props.PeakGain = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
     }
 }
-void Autowah_setParamfv(EffectProps *props,  ALenum param, const float *vals)
-{ Autowah_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(AutowahProps &props,  ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Autowah_setParami(EffectProps*, ALenum param, int)
+void EffectHandler::GetParami(const AutowahProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
-void Autowah_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
         param};
 }
 
-void Autowah_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_AUTOWAH_ATTACK_TIME:
-        *val = props->Autowah.AttackTime;
-        break;
-
-    case AL_AUTOWAH_RELEASE_TIME:
-        *val = props->Autowah.ReleaseTime;
-        break;
-
-    case AL_AUTOWAH_RESONANCE:
-        *val = props->Autowah.Resonance;
-        break;
-
-    case AL_AUTOWAH_PEAK_GAIN:
-        *val = props->Autowah.PeakGain;
-        break;
+    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};
     }
 
 }
-void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Autowah_getParamf(props, param, vals); }
-
-void Autowah_getParami(const EffectProps*, ALenum param, int*)
-{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
-void Autowah_getParamiv(const EffectProps*, ALenum param, int*)
-{
-    throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
-        param};
-}
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
-    props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
-    props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
-    props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Autowah);
-
-const EffectProps AutowahEffectProps{genDefaultProps()};
+void EffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxAutoWahEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1;
-    EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1;
-    EaxAutoWahEffectDirtyFlagsValue lResonance : 1;
-    EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1;
-}; // EaxAutoWahEffectDirtyFlags
-
-
-class EaxAutoWahEffect final :
-    public EaxEffect
-{
-public:
-    EaxAutoWahEffect();
-
+using AutowahCommitter = EaxCommitter<EaxAutowahCommitter>;
 
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXAUTOWAHPROPERTIES eax_{};
-    EAXAUTOWAHPROPERTIES eax_d_{};
-    EaxAutoWahEffectDirtyFlags eax_dirty_flags_{};
-
-
-    void set_eax_defaults();
-
-
-    void set_efx_attack_time();
-
-    void set_efx_release_time();
-
-    void set_efx_resonance();
-
-    void set_efx_peak_gain();
-
-    void set_efx_defaults();
-
-
-    void get(const EaxEaxCall& eax_call);
-
-
-    void validate_attack_time(
-        float flAttackTime);
-
-    void validate_release_time(
-        float flReleaseTime);
-
-    void validate_resonance(
-        long lResonance);
-
-    void validate_peak_level(
-        long lPeakLevel);
-
-    void validate_all(
-        const EAXAUTOWAHPROPERTIES& eax_all);
-
-
-    void defer_attack_time(
-        float flAttackTime);
-
-    void defer_release_time(
-        float flReleaseTime);
-
-    void defer_resonance(
-        long lResonance);
-
-    void defer_peak_level(
-        long lPeakLevel);
-
-    void defer_all(
-        const EAXAUTOWAHPROPERTIES& eax_all);
-
-
-    void defer_attack_time(
-        const EaxEaxCall& eax_call);
-
-    void defer_release_time(
-        const EaxEaxCall& eax_call);
-
-    void defer_resonance(
-        const EaxEaxCall& eax_call);
-
-    void defer_peak_level(
-        const EaxEaxCall& eax_call);
-
-    void defer_all(
-        const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxAutoWahEffect
-
-
-class EaxAutoWahEffectException :
-    public EaxException
-{
-public:
-    explicit EaxAutoWahEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_AUTO_WAH_EFFECT", message}
+struct AttackTimeValidator {
+    void operator()(float flAttackTime) const
     {
+        eax_validate_range<AutowahCommitter::Exception>(
+            "Attack Time",
+            flAttackTime,
+            EAXAUTOWAH_MINATTACKTIME,
+            EAXAUTOWAH_MAXATTACKTIME);
     }
-}; // EaxAutoWahEffectException
-
-
-EaxAutoWahEffect::EaxAutoWahEffect()
-    : EaxEffect{AL_EFFECT_AUTOWAH}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxAutoWahEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxAutoWahEffect::set_eax_defaults()
-{
-    eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
-    eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
-    eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
-    eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
-
-    eax_d_ = eax_;
-}
-
-void EaxAutoWahEffect::set_efx_attack_time()
-{
-    const auto attack_time = clamp(
-        eax_.flAttackTime,
-        AL_AUTOWAH_MIN_ATTACK_TIME,
-        AL_AUTOWAH_MAX_ATTACK_TIME);
-
-    al_effect_props_.Autowah.AttackTime = attack_time;
-}
-
-void EaxAutoWahEffect::set_efx_release_time()
-{
-    const auto release_time = clamp(
-        eax_.flReleaseTime,
-        AL_AUTOWAH_MIN_RELEASE_TIME,
-        AL_AUTOWAH_MAX_RELEASE_TIME);
-
-    al_effect_props_.Autowah.ReleaseTime = release_time;
-}
-
-void EaxAutoWahEffect::set_efx_resonance()
-{
-    const auto resonance = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lResonance)),
-        AL_AUTOWAH_MIN_RESONANCE,
-        AL_AUTOWAH_MAX_RESONANCE);
-
-    al_effect_props_.Autowah.Resonance = resonance;
-}
-
-void EaxAutoWahEffect::set_efx_peak_gain()
-{
-    const auto peak_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lPeakLevel)),
-        AL_AUTOWAH_MIN_PEAK_GAIN,
-        AL_AUTOWAH_MAX_PEAK_GAIN);
-
-    al_effect_props_.Autowah.PeakGain = peak_gain;
-}
-
-void EaxAutoWahEffect::set_efx_defaults()
-{
-    set_efx_attack_time();
-    set_efx_release_time();
-    set_efx_resonance();
-    set_efx_peak_gain();
-}
+}; // AttackTimeValidator
 
-void EaxAutoWahEffect::get(const EaxEaxCall& eax_call)
-{
-    switch (eax_call.get_property_id())
+struct ReleaseTimeValidator {
+    void operator()(float flReleaseTime) const
     {
-        case EAXAUTOWAH_NONE:
-            break;
-
-        case EAXAUTOWAH_ALLPARAMETERS:
-            eax_call.set_value<EaxAutoWahEffectException>(eax_);
-            break;
-
-        case EAXAUTOWAH_ATTACKTIME:
-            eax_call.set_value<EaxAutoWahEffectException>(eax_.flAttackTime);
-            break;
-
-        case EAXAUTOWAH_RELEASETIME:
-            eax_call.set_value<EaxAutoWahEffectException>(eax_.flReleaseTime);
-            break;
-
-        case EAXAUTOWAH_RESONANCE:
-            eax_call.set_value<EaxAutoWahEffectException>(eax_.lResonance);
-            break;
-
-        case EAXAUTOWAH_PEAKLEVEL:
-            eax_call.set_value<EaxAutoWahEffectException>(eax_.lPeakLevel);
-            break;
-
-        default:
-            throw EaxAutoWahEffectException{"Unsupported property id."};
+        eax_validate_range<AutowahCommitter::Exception>(
+            "Release Time",
+            flReleaseTime,
+            EAXAUTOWAH_MINRELEASETIME,
+            EAXAUTOWAH_MAXRELEASETIME);
     }
-}
+}; // ReleaseTimeValidator
 
-void EaxAutoWahEffect::validate_attack_time(
-    float flAttackTime)
-{
-    eax_validate_range<EaxAutoWahEffectException>(
-        "Attack Time",
-        flAttackTime,
-        EAXAUTOWAH_MINATTACKTIME,
-        EAXAUTOWAH_MAXATTACKTIME);
-}
-
-void EaxAutoWahEffect::validate_release_time(
-    float flReleaseTime)
-{
-    eax_validate_range<EaxAutoWahEffectException>(
-        "Release Time",
-        flReleaseTime,
-        EAXAUTOWAH_MINRELEASETIME,
-        EAXAUTOWAH_MAXRELEASETIME);
-}
-
-void EaxAutoWahEffect::validate_resonance(
-    long lResonance)
-{
-    eax_validate_range<EaxAutoWahEffectException>(
-        "Resonance",
-        lResonance,
-        EAXAUTOWAH_MINRESONANCE,
-        EAXAUTOWAH_MAXRESONANCE);
-}
-
-void EaxAutoWahEffect::validate_peak_level(
-    long lPeakLevel)
-{
-    eax_validate_range<EaxAutoWahEffectException>(
-        "Peak Level",
-        lPeakLevel,
-        EAXAUTOWAH_MINPEAKLEVEL,
-        EAXAUTOWAH_MAXPEAKLEVEL);
-}
-
-void EaxAutoWahEffect::validate_all(
-    const EAXAUTOWAHPROPERTIES& eax_all)
-{
-    validate_attack_time(eax_all.flAttackTime);
-    validate_release_time(eax_all.flReleaseTime);
-    validate_resonance(eax_all.lResonance);
-    validate_peak_level(eax_all.lPeakLevel);
-}
-
-void EaxAutoWahEffect::defer_attack_time(
-    float flAttackTime)
-{
-    eax_d_.flAttackTime = flAttackTime;
-    eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime);
-}
-
-void EaxAutoWahEffect::defer_release_time(
-    float flReleaseTime)
-{
-    eax_d_.flReleaseTime = flReleaseTime;
-    eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime);
-}
-
-void EaxAutoWahEffect::defer_resonance(
-    long lResonance)
-{
-    eax_d_.lResonance = lResonance;
-    eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance);
-}
+struct ResonanceValidator {
+    void operator()(long lResonance) const
+    {
+        eax_validate_range<AutowahCommitter::Exception>(
+            "Resonance",
+            lResonance,
+            EAXAUTOWAH_MINRESONANCE,
+            EAXAUTOWAH_MAXRESONANCE);
+    }
+}; // ResonanceValidator
 
-void EaxAutoWahEffect::defer_peak_level(
-    long lPeakLevel)
-{
-    eax_d_.lPeakLevel = lPeakLevel;
-    eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel);
-}
+struct PeakLevelValidator {
+    void operator()(long lPeakLevel) const
+    {
+        eax_validate_range<AutowahCommitter::Exception>(
+            "Peak Level",
+            lPeakLevel,
+            EAXAUTOWAH_MINPEAKLEVEL,
+            EAXAUTOWAH_MAXPEAKLEVEL);
+    }
+}; // PeakLevelValidator
 
-void EaxAutoWahEffect::defer_all(
-    const EAXAUTOWAHPROPERTIES& eax_all)
-{
-    validate_all(eax_all);
+struct AllValidator {
+    void operator()(const EAXAUTOWAHPROPERTIES& all) const
+    {
+        AttackTimeValidator{}(all.flAttackTime);
+        ReleaseTimeValidator{}(all.flReleaseTime);
+        ResonanceValidator{}(all.lResonance);
+        PeakLevelValidator{}(all.lPeakLevel);
+    }
+}; // AllValidator
 
-    defer_attack_time(eax_all.flAttackTime);
-    defer_release_time(eax_all.flReleaseTime);
-    defer_resonance(eax_all.lResonance);
-    defer_peak_level(eax_all.lPeakLevel);
-}
+} // namespace
 
-void EaxAutoWahEffect::defer_attack_time(
-    const EaxEaxCall& eax_call)
+template<>
+struct AutowahCommitter::Exception : public EaxException
 {
-    const auto& attack_time =
-        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flAttackTime)>();
-
-    validate_attack_time(attack_time);
-    defer_attack_time(attack_time);
-}
+    explicit Exception(const char *message) : EaxException{"EAX_AUTOWAH_EFFECT", message}
+    { }
+};
 
-void EaxAutoWahEffect::defer_release_time(
-    const EaxEaxCall& eax_call)
+template<>
+[[noreturn]] void AutowahCommitter::fail(const char *message)
 {
-    const auto& release_time =
-        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flReleaseTime)>();
-
-    validate_release_time(release_time);
-    defer_release_time(release_time);
+    throw Exception{message};
 }
 
-void EaxAutoWahEffect::defer_resonance(
-    const EaxEaxCall& eax_call)
+bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props)
 {
-    const auto& resonance =
-        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lResonance)>();
-
-    validate_resonance(resonance);
-    defer_resonance(resonance);
-}
+    if(auto *cur = std::get_if<EAXAUTOWAHPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-void EaxAutoWahEffect::defer_peak_level(
-    const EaxEaxCall& eax_call)
-{
-    const auto& peak_level =
-        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lPeakLevel)>();
+    mEaxProps = props;
+    mAlProps = [&]{
+        AutowahProps ret{};
+        ret.AttackTime = props.flAttackTime;
+        ret.ReleaseTime = props.flReleaseTime;
+        ret.Resonance = level_mb_to_gain(static_cast<float>(props.lResonance));
+        ret.PeakGain = level_mb_to_gain(static_cast<float>(props.lPeakLevel));
+        return ret;
+    }();
 
-    validate_peak_level(peak_level);
-    defer_peak_level(peak_level);
+    return true;
 }
 
-void EaxAutoWahEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& all =
-        eax_call.get_value<EaxAutoWahEffectException, const EAXAUTOWAHPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    static constexpr EAXAUTOWAHPROPERTIES defprops{[]
+    {
+        EAXAUTOWAHPROPERTIES ret{};
+        ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
+        ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
+        ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
+        ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
+        return ret;
+    }()};
+    props = defprops;
 }
 
-// [[nodiscard]]
-bool EaxAutoWahEffect::apply_deferred()
+void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props)
 {
-    if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.flAttackTime)
-    {
-        set_efx_attack_time();
-    }
-
-    if (eax_dirty_flags_.flReleaseTime)
-    {
-        set_efx_release_time();
-    }
-
-    if (eax_dirty_flags_.lResonance)
-    {
-        set_efx_resonance();
-    }
-
-    if (eax_dirty_flags_.lPeakLevel)
+    switch(call.get_property_id())
     {
-        set_efx_peak_gain();
+    case EAXAUTOWAH_NONE: break;
+    case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.flAttackTime); break;
+    case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.flReleaseTime); break;
+    case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.lResonance); break;
+    case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.lPeakLevel); break;
+    default: fail_unknown_property_id();
     }
-
-    eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxAutoWahEffect::set(const EaxEaxCall& eax_call)
+void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props)
 {
-    switch (eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXAUTOWAH_NONE:
-            break;
-
-        case EAXAUTOWAH_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXAUTOWAH_ATTACKTIME:
-            defer_attack_time(eax_call);
-            break;
-
-        case EAXAUTOWAH_RELEASETIME:
-            defer_release_time(eax_call);
-            break;
-
-        case EAXAUTOWAH_RESONANCE:
-            defer_resonance(eax_call);
-            break;
-
-        case EAXAUTOWAH_PEAKLEVEL:
-            defer_peak_level(eax_call);
-            break;
-
-        default:
-            throw EaxAutoWahEffectException{"Unsupported property id."};
+    case EAXAUTOWAH_NONE: break;
+    case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.flAttackTime); break;
+    case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.flReleaseTime); break;
+    case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.lResonance); break;
+    case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.lPeakLevel); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_auto_wah_effect()
-{
-    return std::make_unique<::EaxAutoWahEffect>();
-}
-
 #endif // ALSOFT_EAX

+ 405 - 1110
libs/openal-soft/al/effects/chorus.cpp

@@ -1,23 +1,22 @@
 
 #include "config.h"
 
+#include <optional>
 #include <stdexcept>
 
 #include "AL/al.h"
 #include "AL/efx.h"
 
 #include "alc/effects/base.h"
-#include "aloptional.h"
 #include "core/logging.h"
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
 #include <cassert>
-
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
@@ -29,16 +28,16 @@ static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too sm
 static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
 static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
 
-inline al::optional<ChorusWaveform> WaveformFromEnum(ALenum type)
+constexpr std::optional<ChorusWaveform> WaveformFromEnum(ALenum type) noexcept
 {
     switch(type)
     {
-    case AL_CHORUS_WAVEFORM_SINUSOID: return al::make_optional(ChorusWaveform::Sinusoid);
-    case AL_CHORUS_WAVEFORM_TRIANGLE: return al::make_optional(ChorusWaveform::Triangle);
+    case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid;
+    case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle;
     }
-    return al::nullopt;
+    return std::nullopt;
 }
-inline ALenum EnumFromWaveform(ChorusWaveform type)
+constexpr ALenum EnumFromWaveform(ChorusWaveform type)
 {
     switch(type)
     {
@@ -48,13 +47,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type)
     throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))};
 }
 
-void Chorus_setParami(EffectProps *props, ALenum param, int val)
+constexpr EffectProps genDefaultChorusProps() noexcept
+{
+    ChorusProps props{};
+    props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value();
+    props.Phase = AL_CHORUS_DEFAULT_PHASE;
+    props.Rate = AL_CHORUS_DEFAULT_RATE;
+    props.Depth = AL_CHORUS_DEFAULT_DEPTH;
+    props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
+    props.Delay = AL_CHORUS_DEFAULT_DELAY;
+    return props;
+}
+
+constexpr EffectProps genDefaultFlangerProps() noexcept
+{
+    FlangerProps props{};
+    props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value();
+    props.Phase = AL_FLANGER_DEFAULT_PHASE;
+    props.Rate = AL_FLANGER_DEFAULT_RATE;
+    props.Depth = AL_FLANGER_DEFAULT_DEPTH;
+    props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
+    props.Delay = AL_FLANGER_DEFAULT_DELAY;
+    return props;
+}
+
+} // namespace
+
+const EffectProps ChorusEffectProps{genDefaultChorusProps()};
+
+void EffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_CHORUS_WAVEFORM:
         if(auto formopt = WaveformFromEnum(val))
-            props->Chorus.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val};
         break;
@@ -62,115 +89,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val)
     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};
-        props->Chorus.Phase = val;
+        props.Phase = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
     }
 }
-void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Chorus_setParami(props, param, vals[0]); }
-void Chorus_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, *vals); }
+void EffectHandler::SetParamf(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};
-        props->Chorus.Rate = val;
+        props.Rate = val;
         break;
 
     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};
-        props->Chorus.Depth = val;
+        props.Depth = val;
         break;
 
     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};
-        props->Chorus.Feedback = val;
+        props.Feedback = val;
         break;
 
     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};
-        props->Chorus.Delay = val;
+        props.Delay = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
     }
 }
-void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Chorus_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Chorus_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_CHORUS_WAVEFORM:
-        *val = EnumFromWaveform(props->Chorus.Waveform);
-        break;
-
-    case AL_CHORUS_PHASE:
-        *val = props->Chorus.Phase;
-        break;
+    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};
     }
 }
-void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Chorus_getParami(props, param, vals); }
-void Chorus_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_CHORUS_RATE:
-        *val = props->Chorus.Rate;
-        break;
-
-    case AL_CHORUS_DEPTH:
-        *val = props->Chorus.Depth;
-        break;
-
-    case AL_CHORUS_FEEDBACK:
-        *val = props->Chorus.Feedback;
-        break;
-
-    case AL_CHORUS_DELAY:
-        *val = props->Chorus.Delay;
-        break;
+    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};
     }
 }
-void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Chorus_getParamf(props, param, vals); }
+void EffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-const EffectProps genDefaultChorusProps() noexcept
-{
-    EffectProps props{};
-    props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM);
-    props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
-    props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
-    props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
-    props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
-    props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
-    return props;
-}
 
+const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
 
-void Flanger_setParami(EffectProps *props, ALenum param, int val)
+void EffectHandler::SetParami(FlangerProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_FLANGER_WAVEFORM:
         if(auto formopt = WaveformFromEnum(val))
-            props->Chorus.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val};
         break;
@@ -178,1180 +179,474 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val)
     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};
-        props->Chorus.Phase = val;
+        props.Phase = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
     }
 }
-void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Flanger_setParami(props, param, vals[0]); }
-void Flanger_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamiv(FlangerProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, *vals); }
+void EffectHandler::SetParamf(FlangerProps &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};
-        props->Chorus.Rate = val;
+        props.Rate = val;
         break;
 
     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};
-        props->Chorus.Depth = val;
+        props.Depth = val;
         break;
 
     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};
-        props->Chorus.Feedback = val;
+        props.Feedback = val;
         break;
 
     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};
-        props->Chorus.Delay = val;
+        props.Delay = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
     }
 }
-void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Flanger_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(FlangerProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Flanger_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const FlangerProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_FLANGER_WAVEFORM:
-        *val = EnumFromWaveform(props->Chorus.Waveform);
-        break;
-
-    case AL_FLANGER_PHASE:
-        *val = props->Chorus.Phase;
-        break;
+    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};
     }
 }
-void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Flanger_getParami(props, param, vals); }
-void Flanger_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamiv(const FlangerProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const FlangerProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_FLANGER_RATE:
-        *val = props->Chorus.Rate;
-        break;
-
-    case AL_FLANGER_DEPTH:
-        *val = props->Chorus.Depth;
-        break;
-
-    case AL_FLANGER_FEEDBACK:
-        *val = props->Chorus.Feedback;
-        break;
-
-    case AL_FLANGER_DELAY:
-        *val = props->Chorus.Delay;
-        break;
+    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};
     }
 }
-void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Flanger_getParamf(props, param, vals); }
-
-EffectProps genDefaultFlangerProps() noexcept
-{
-    EffectProps props{};
-    props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM);
-    props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
-    props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
-    props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
-    props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
-    props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Chorus);
-
-const EffectProps ChorusEffectProps{genDefaultChorusProps()};
-
-DEFINE_ALEFFECT_VTABLE(Flanger);
-
-const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
+void EffectHandler::GetParamfv(const FlangerProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
 
 #ifdef ALSOFT_EAX
 namespace {
 
-void eax_set_efx_waveform(
-    ALenum waveform,
-    EffectProps& al_effect_props)
-{
-    const auto efx_waveform = WaveformFromEnum(waveform);
-    assert(efx_waveform.has_value());
-    al_effect_props.Chorus.Waveform = *efx_waveform;
-}
-
-void eax_set_efx_phase(
-    ALint phase,
-    EffectProps& al_effect_props)
-{
-    al_effect_props.Chorus.Phase = phase;
-}
-
-void eax_set_efx_rate(
-    ALfloat rate,
-    EffectProps& al_effect_props)
-{
-    al_effect_props.Chorus.Rate = rate;
-}
-
-void eax_set_efx_depth(
-    ALfloat depth,
-    EffectProps& al_effect_props)
-{
-    al_effect_props.Chorus.Depth = depth;
-}
-
-void eax_set_efx_feedback(
-    ALfloat feedback,
-    EffectProps& al_effect_props)
-{
-    al_effect_props.Chorus.Feedback = feedback;
-}
-
-void eax_set_efx_delay(
-    ALfloat delay,
-    EffectProps& al_effect_props)
-{
-    al_effect_props.Chorus.Delay = delay;
-}
-
-
-using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxChorusEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxChorusEffectDirtyFlagsValue ulWaveform : 1;
-    EaxChorusEffectDirtyFlagsValue lPhase : 1;
-    EaxChorusEffectDirtyFlagsValue flRate : 1;
-    EaxChorusEffectDirtyFlagsValue flDepth : 1;
-    EaxChorusEffectDirtyFlagsValue flFeedback : 1;
-    EaxChorusEffectDirtyFlagsValue flDelay : 1;
-}; // EaxChorusEffectDirtyFlags
-
-
-class EaxChorusEffect final :
-    public EaxEffect
-{
-public:
-    EaxChorusEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXCHORUSPROPERTIES eax_{};
-    EAXCHORUSPROPERTIES eax_d_{};
-    EaxChorusEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults() noexcept;
-
-    void set_efx_waveform();
-    void set_efx_phase();
-    void set_efx_rate();
-    void set_efx_depth();
-    void set_efx_feedback();
-    void set_efx_delay();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_waveform(unsigned long ulWaveform);
-    void validate_phase(long lPhase);
-    void validate_rate(float flRate);
-    void validate_depth(float flDepth);
-    void validate_feedback(float flFeedback);
-    void validate_delay(float flDelay);
-    void validate_all(const EAXCHORUSPROPERTIES& eax_all);
-
-    void defer_waveform(unsigned long ulWaveform);
-    void defer_phase(long lPhase);
-    void defer_rate(float flRate);
-    void defer_depth(float flDepth);
-    void defer_feedback(float flFeedback);
-    void defer_delay(float flDelay);
-    void defer_all(const EAXCHORUSPROPERTIES& eax_all);
-
-    void defer_waveform(const EaxEaxCall& eax_call);
-    void defer_phase(const EaxEaxCall& eax_call);
-    void defer_rate(const EaxEaxCall& eax_call);
-    void defer_depth(const EaxEaxCall& eax_call);
-    void defer_feedback(const EaxEaxCall& eax_call);
-    void defer_delay(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxChorusEffect
-
-
-class EaxChorusEffectException :
-    public EaxException
-{
-public:
-    explicit EaxChorusEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_CHORUS_EFFECT", message}
-    {
-    }
-}; // EaxChorusEffectException
-
-
-EaxChorusEffect::EaxChorusEffect()
-    : EaxEffect{AL_EFFECT_CHORUS}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxChorusEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxChorusEffect::set_eax_defaults() noexcept
-{
-    eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM;
-    eax_.lPhase = EAXCHORUS_DEFAULTPHASE;
-    eax_.flRate = EAXCHORUS_DEFAULTRATE;
-    eax_.flDepth = EAXCHORUS_DEFAULTDEPTH;
-    eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK;
-    eax_.flDelay = EAXCHORUS_DEFAULTDELAY;
-
-    eax_d_ = eax_;
-}
-
-void EaxChorusEffect::set_efx_waveform()
-{
-    const auto waveform = clamp(
-        static_cast<ALint>(eax_.ulWaveform),
-        AL_CHORUS_MIN_WAVEFORM,
-        AL_CHORUS_MAX_WAVEFORM);
-
-    eax_set_efx_waveform(waveform, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_phase()
-{
-    const auto phase = clamp(
-        static_cast<ALint>(eax_.lPhase),
-        AL_CHORUS_MIN_PHASE,
-        AL_CHORUS_MAX_PHASE);
-
-    eax_set_efx_phase(phase, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_rate()
-{
-    const auto rate = clamp(
-        eax_.flRate,
-        AL_CHORUS_MIN_RATE,
-        AL_CHORUS_MAX_RATE);
-
-    eax_set_efx_rate(rate, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_depth()
-{
-    const auto depth = clamp(
-        eax_.flDepth,
-        AL_CHORUS_MIN_DEPTH,
-        AL_CHORUS_MAX_DEPTH);
-
-    eax_set_efx_depth(depth, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_feedback()
-{
-    const auto feedback = clamp(
-        eax_.flFeedback,
-        AL_CHORUS_MIN_FEEDBACK,
-        AL_CHORUS_MAX_FEEDBACK);
-
-    eax_set_efx_feedback(feedback, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_delay()
-{
-    const auto delay = clamp(
-        eax_.flDelay,
-        AL_CHORUS_MIN_DELAY,
-        AL_CHORUS_MAX_DELAY);
-
-    eax_set_efx_delay(delay, al_effect_props_);
-}
-
-void EaxChorusEffect::set_efx_defaults()
-{
-    set_efx_waveform();
-    set_efx_phase();
-    set_efx_rate();
-    set_efx_depth();
-    set_efx_feedback();
-    set_efx_delay();
-}
-
-void EaxChorusEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
-    {
-        case EAXCHORUS_NONE:
-            break;
-
-        case EAXCHORUS_ALLPARAMETERS:
-            eax_call.set_value<EaxChorusEffectException>(eax_);
-            break;
-
-        case EAXCHORUS_WAVEFORM:
-            eax_call.set_value<EaxChorusEffectException>(eax_.ulWaveform);
-            break;
-
-        case EAXCHORUS_PHASE:
-            eax_call.set_value<EaxChorusEffectException>(eax_.lPhase);
-            break;
-
-        case EAXCHORUS_RATE:
-            eax_call.set_value<EaxChorusEffectException>(eax_.flRate);
-            break;
-
-        case EAXCHORUS_DEPTH:
-            eax_call.set_value<EaxChorusEffectException>(eax_.flDepth);
-            break;
-
-        case EAXCHORUS_FEEDBACK:
-            eax_call.set_value<EaxChorusEffectException>(eax_.flFeedback);
-            break;
-
-        case EAXCHORUS_DELAY:
-            eax_call.set_value<EaxChorusEffectException>(eax_.flDelay);
-            break;
-
-        default:
-            throw EaxChorusEffectException{"Unsupported property id."};
-    }
-}
-
-void EaxChorusEffect::validate_waveform(
-    unsigned long ulWaveform)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Waveform",
-        ulWaveform,
-        EAXCHORUS_MINWAVEFORM,
-        EAXCHORUS_MAXWAVEFORM);
-}
-
-void EaxChorusEffect::validate_phase(
-    long lPhase)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Phase",
-        lPhase,
-        EAXCHORUS_MINPHASE,
-        EAXCHORUS_MAXPHASE);
-}
-
-void EaxChorusEffect::validate_rate(
-    float flRate)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Rate",
-        flRate,
-        EAXCHORUS_MINRATE,
-        EAXCHORUS_MAXRATE);
-}
-
-void EaxChorusEffect::validate_depth(
-    float flDepth)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Depth",
-        flDepth,
-        EAXCHORUS_MINDEPTH,
-        EAXCHORUS_MAXDEPTH);
-}
-
-void EaxChorusEffect::validate_feedback(
-    float flFeedback)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Feedback",
-        flFeedback,
-        EAXCHORUS_MINFEEDBACK,
-        EAXCHORUS_MAXFEEDBACK);
-}
-
-void EaxChorusEffect::validate_delay(
-    float flDelay)
-{
-    eax_validate_range<EaxChorusEffectException>(
-        "Delay",
-        flDelay,
-        EAXCHORUS_MINDELAY,
-        EAXCHORUS_MAXDELAY);
-}
-
-void EaxChorusEffect::validate_all(
-    const EAXCHORUSPROPERTIES& eax_all)
-{
-    validate_waveform(eax_all.ulWaveform);
-    validate_phase(eax_all.lPhase);
-    validate_rate(eax_all.flRate);
-    validate_depth(eax_all.flDepth);
-    validate_feedback(eax_all.flFeedback);
-    validate_delay(eax_all.flDelay);
-}
-
-void EaxChorusEffect::defer_waveform(
-    unsigned long ulWaveform)
-{
-    eax_d_.ulWaveform = ulWaveform;
-    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
-}
-
-void EaxChorusEffect::defer_phase(
-    long lPhase)
-{
-    eax_d_.lPhase = lPhase;
-    eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase);
-}
-
-void EaxChorusEffect::defer_rate(
-    float flRate)
-{
-    eax_d_.flRate = flRate;
-    eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
-}
-
-void EaxChorusEffect::defer_depth(
-    float flDepth)
-{
-    eax_d_.flDepth = flDepth;
-    eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth);
-}
-
-void EaxChorusEffect::defer_feedback(
-    float flFeedback)
-{
-    eax_d_.flFeedback = flFeedback;
-    eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
-}
-
-void EaxChorusEffect::defer_delay(
-    float flDelay)
-{
-    eax_d_.flDelay = flDelay;
-    eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
-}
-
-void EaxChorusEffect::defer_all(
-    const EAXCHORUSPROPERTIES& eax_all)
-{
-    defer_waveform(eax_all.ulWaveform);
-    defer_phase(eax_all.lPhase);
-    defer_rate(eax_all.flRate);
-    defer_depth(eax_all.flDepth);
-    defer_feedback(eax_all.flFeedback);
-    defer_delay(eax_all.flDelay);
-}
-
-void EaxChorusEffect::defer_waveform(
-    const EaxEaxCall& eax_call)
-{
-    const auto& waveform =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::ulWaveform)>();
-
-    validate_waveform(waveform);
-    defer_waveform(waveform);
-}
-
-void EaxChorusEffect::defer_phase(
-    const EaxEaxCall& eax_call)
-{
-    const auto& phase =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::lPhase)>();
-
-    validate_phase(phase);
-    defer_phase(phase);
-}
-
-void EaxChorusEffect::defer_rate(
-    const EaxEaxCall& eax_call)
-{
-    const auto& rate =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flRate)>();
-
-    validate_rate(rate);
-    defer_rate(rate);
-}
-
-void EaxChorusEffect::defer_depth(
-    const EaxEaxCall& eax_call)
-{
-    const auto& depth =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDepth)>();
-
-    validate_depth(depth);
-    defer_depth(depth);
-}
-
-void EaxChorusEffect::defer_feedback(
-    const EaxEaxCall& eax_call)
-{
-    const auto& feedback =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flFeedback)>();
-
-    validate_feedback(feedback);
-    defer_feedback(feedback);
-}
-
-void EaxChorusEffect::defer_delay(
-    const EaxEaxCall& eax_call)
-{
-    const auto& delay =
-        eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDelay)>();
-
-    validate_delay(delay);
-    defer_delay(delay);
-}
-
-void EaxChorusEffect::defer_all(
-    const EaxEaxCall& eax_call)
-{
-    const auto& all =
-        eax_call.get_value<EaxChorusEffectException, const EAXCHORUSPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
-}
-
-// [[nodiscard]]
-bool EaxChorusEffect::apply_deferred()
-{
-    if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{})
+struct EaxChorusTraits {
+    using EaxProps = EAXCHORUSPROPERTIES;
+    using Committer = EaxChorusCommitter;
+    using AlProps = ChorusProps;
+
+    static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; }
+
+    static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; }
+    static constexpr auto eax_allparameters_param_id() { return EAXCHORUS_ALLPARAMETERS; }
+    static constexpr auto eax_waveform_param_id() { return EAXCHORUS_WAVEFORM; }
+    static constexpr auto eax_phase_param_id() { return EAXCHORUS_PHASE; }
+    static constexpr auto eax_rate_param_id() { return EAXCHORUS_RATE; }
+    static constexpr auto eax_depth_param_id() { return EAXCHORUS_DEPTH; }
+    static constexpr auto eax_feedback_param_id() { return EAXCHORUS_FEEDBACK; }
+    static constexpr auto eax_delay_param_id() { return EAXCHORUS_DELAY; }
+
+    static constexpr auto eax_min_waveform() { return EAXCHORUS_MINWAVEFORM; }
+    static constexpr auto eax_min_phase() { return EAXCHORUS_MINPHASE; }
+    static constexpr auto eax_min_rate() { return EAXCHORUS_MINRATE; }
+    static constexpr auto eax_min_depth() { return EAXCHORUS_MINDEPTH; }
+    static constexpr auto eax_min_feedback() { return EAXCHORUS_MINFEEDBACK; }
+    static constexpr auto eax_min_delay() { return EAXCHORUS_MINDELAY; }
+
+    static constexpr auto eax_max_waveform() { return EAXCHORUS_MAXWAVEFORM; }
+    static constexpr auto eax_max_phase() { return EAXCHORUS_MAXPHASE; }
+    static constexpr auto eax_max_rate() { return EAXCHORUS_MAXRATE; }
+    static constexpr auto eax_max_depth() { return EAXCHORUS_MAXDEPTH; }
+    static constexpr auto eax_max_feedback() { return EAXCHORUS_MAXFEEDBACK; }
+    static constexpr auto eax_max_delay() { return EAXCHORUS_MAXDELAY; }
+
+    static constexpr auto eax_default_waveform() { return EAXCHORUS_DEFAULTWAVEFORM; }
+    static constexpr auto eax_default_phase() { return EAXCHORUS_DEFAULTPHASE; }
+    static constexpr auto eax_default_rate() { return EAXCHORUS_DEFAULTRATE; }
+    static constexpr auto eax_default_depth() { return EAXCHORUS_DEFAULTDEPTH; }
+    static constexpr auto eax_default_feedback() { return EAXCHORUS_DEFAULTFEEDBACK; }
+    static constexpr auto eax_default_delay() { return EAXCHORUS_DEFAULTDELAY; }
+
+    static constexpr auto efx_min_waveform() { return AL_CHORUS_MIN_WAVEFORM; }
+    static constexpr auto efx_min_phase() { return AL_CHORUS_MIN_PHASE; }
+    static constexpr auto efx_min_rate() { return AL_CHORUS_MIN_RATE; }
+    static constexpr auto efx_min_depth() { return AL_CHORUS_MIN_DEPTH; }
+    static constexpr auto efx_min_feedback() { return AL_CHORUS_MIN_FEEDBACK; }
+    static constexpr auto efx_min_delay() { return AL_CHORUS_MIN_DELAY; }
+
+    static constexpr auto efx_max_waveform() { return AL_CHORUS_MAX_WAVEFORM; }
+    static constexpr auto efx_max_phase() { return AL_CHORUS_MAX_PHASE; }
+    static constexpr auto efx_max_rate() { return AL_CHORUS_MAX_RATE; }
+    static constexpr auto efx_max_depth() { return AL_CHORUS_MAX_DEPTH; }
+    static constexpr auto efx_max_feedback() { return AL_CHORUS_MAX_FEEDBACK; }
+    static constexpr auto efx_max_delay() { return AL_CHORUS_MAX_DELAY; }
+
+    static constexpr auto efx_default_waveform() { return AL_CHORUS_DEFAULT_WAVEFORM; }
+    static constexpr auto efx_default_phase() { return AL_CHORUS_DEFAULT_PHASE; }
+    static constexpr auto efx_default_rate() { return AL_CHORUS_DEFAULT_RATE; }
+    static constexpr auto efx_default_depth() { return AL_CHORUS_DEFAULT_DEPTH; }
+    static constexpr auto efx_default_feedback() { return AL_CHORUS_DEFAULT_FEEDBACK; }
+    static constexpr auto efx_default_delay() { return AL_CHORUS_DEFAULT_DELAY; }
+
+    static ChorusWaveform eax_waveform(unsigned long type)
     {
-        return false;
+        if(type == EAX_CHORUS_SINUSOID) return ChorusWaveform::Sinusoid;
+        if(type == EAX_CHORUS_TRIANGLE) return ChorusWaveform::Triangle;
+        return ChorusWaveform::Sinusoid;
     }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.ulWaveform)
+}; // EaxChorusTraits
+
+struct EaxFlangerTraits {
+    using EaxProps = EAXFLANGERPROPERTIES;
+    using Committer = EaxFlangerCommitter;
+    using AlProps = FlangerProps;
+
+    static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; }
+
+    static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; }
+    static constexpr auto eax_allparameters_param_id() { return EAXFLANGER_ALLPARAMETERS; }
+    static constexpr auto eax_waveform_param_id() { return EAXFLANGER_WAVEFORM; }
+    static constexpr auto eax_phase_param_id() { return EAXFLANGER_PHASE; }
+    static constexpr auto eax_rate_param_id() { return EAXFLANGER_RATE; }
+    static constexpr auto eax_depth_param_id() { return EAXFLANGER_DEPTH; }
+    static constexpr auto eax_feedback_param_id() { return EAXFLANGER_FEEDBACK; }
+    static constexpr auto eax_delay_param_id() { return EAXFLANGER_DELAY; }
+
+    static constexpr auto eax_min_waveform() { return EAXFLANGER_MINWAVEFORM; }
+    static constexpr auto eax_min_phase() { return EAXFLANGER_MINPHASE; }
+    static constexpr auto eax_min_rate() { return EAXFLANGER_MINRATE; }
+    static constexpr auto eax_min_depth() { return EAXFLANGER_MINDEPTH; }
+    static constexpr auto eax_min_feedback() { return EAXFLANGER_MINFEEDBACK; }
+    static constexpr auto eax_min_delay() { return EAXFLANGER_MINDELAY; }
+
+    static constexpr auto eax_max_waveform() { return EAXFLANGER_MAXWAVEFORM; }
+    static constexpr auto eax_max_phase() { return EAXFLANGER_MAXPHASE; }
+    static constexpr auto eax_max_rate() { return EAXFLANGER_MAXRATE; }
+    static constexpr auto eax_max_depth() { return EAXFLANGER_MAXDEPTH; }
+    static constexpr auto eax_max_feedback() { return EAXFLANGER_MAXFEEDBACK; }
+    static constexpr auto eax_max_delay() { return EAXFLANGER_MAXDELAY; }
+
+    static constexpr auto eax_default_waveform() { return EAXFLANGER_DEFAULTWAVEFORM; }
+    static constexpr auto eax_default_phase() { return EAXFLANGER_DEFAULTPHASE; }
+    static constexpr auto eax_default_rate() { return EAXFLANGER_DEFAULTRATE; }
+    static constexpr auto eax_default_depth() { return EAXFLANGER_DEFAULTDEPTH; }
+    static constexpr auto eax_default_feedback() { return EAXFLANGER_DEFAULTFEEDBACK; }
+    static constexpr auto eax_default_delay() { return EAXFLANGER_DEFAULTDELAY; }
+
+    static constexpr auto efx_min_waveform() { return AL_FLANGER_MIN_WAVEFORM; }
+    static constexpr auto efx_min_phase() { return AL_FLANGER_MIN_PHASE; }
+    static constexpr auto efx_min_rate() { return AL_FLANGER_MIN_RATE; }
+    static constexpr auto efx_min_depth() { return AL_FLANGER_MIN_DEPTH; }
+    static constexpr auto efx_min_feedback() { return AL_FLANGER_MIN_FEEDBACK; }
+    static constexpr auto efx_min_delay() { return AL_FLANGER_MIN_DELAY; }
+
+    static constexpr auto efx_max_waveform() { return AL_FLANGER_MAX_WAVEFORM; }
+    static constexpr auto efx_max_phase() { return AL_FLANGER_MAX_PHASE; }
+    static constexpr auto efx_max_rate() { return AL_FLANGER_MAX_RATE; }
+    static constexpr auto efx_max_depth() { return AL_FLANGER_MAX_DEPTH; }
+    static constexpr auto efx_max_feedback() { return AL_FLANGER_MAX_FEEDBACK; }
+    static constexpr auto efx_max_delay() { return AL_FLANGER_MAX_DELAY; }
+
+    static constexpr auto efx_default_waveform() { return AL_FLANGER_DEFAULT_WAVEFORM; }
+    static constexpr auto efx_default_phase() { return AL_FLANGER_DEFAULT_PHASE; }
+    static constexpr auto efx_default_rate() { return AL_FLANGER_DEFAULT_RATE; }
+    static constexpr auto efx_default_depth() { return AL_FLANGER_DEFAULT_DEPTH; }
+    static constexpr auto efx_default_feedback() { return AL_FLANGER_DEFAULT_FEEDBACK; }
+    static constexpr auto efx_default_delay() { return AL_FLANGER_DEFAULT_DELAY; }
+
+    static ChorusWaveform eax_waveform(unsigned long type)
     {
-        set_efx_waveform();
+        if(type == EAX_FLANGER_SINUSOID) return ChorusWaveform::Sinusoid;
+        if(type == EAX_FLANGER_TRIANGLE) return ChorusWaveform::Triangle;
+        return ChorusWaveform::Sinusoid;
     }
+}; // EaxFlangerTraits
+
+template<typename TTraits>
+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 {
+        void operator()(unsigned long ulWaveform) const
+        {
+            eax_validate_range<Exception>(
+                "Waveform",
+                ulWaveform,
+                Traits::eax_min_waveform(),
+                Traits::eax_max_waveform());
+        }
+    }; // WaveformValidator
+
+    struct PhaseValidator {
+        void operator()(long lPhase) const
+        {
+            eax_validate_range<Exception>(
+                "Phase",
+                lPhase,
+                Traits::eax_min_phase(),
+                Traits::eax_max_phase());
+        }
+    }; // PhaseValidator
+
+    struct RateValidator {
+        void operator()(float flRate) const
+        {
+            eax_validate_range<Exception>(
+                "Rate",
+                flRate,
+                Traits::eax_min_rate(),
+                Traits::eax_max_rate());
+        }
+    }; // RateValidator
+
+    struct DepthValidator {
+        void operator()(float flDepth) const
+        {
+            eax_validate_range<Exception>(
+                "Depth",
+                flDepth,
+                Traits::eax_min_depth(),
+                Traits::eax_max_depth());
+        }
+    }; // DepthValidator
+
+    struct FeedbackValidator {
+        void operator()(float flFeedback) const
+        {
+            eax_validate_range<Exception>(
+                "Feedback",
+                flFeedback,
+                Traits::eax_min_feedback(),
+                Traits::eax_max_feedback());
+        }
+    }; // FeedbackValidator
+
+    struct DelayValidator {
+        void operator()(float flDelay) const
+        {
+            eax_validate_range<Exception>(
+                "Delay",
+                flDelay,
+                Traits::eax_min_delay(),
+                Traits::eax_max_delay());
+        }
+    }; // DelayValidator
+
+    struct AllValidator {
+        void operator()(const EaxProps& all) const
+        {
+            WaveformValidator{}(all.ulWaveform);
+            PhaseValidator{}(all.lPhase);
+            RateValidator{}(all.flRate);
+            DepthValidator{}(all.flDepth);
+            FeedbackValidator{}(all.flFeedback);
+            DelayValidator{}(all.flDelay);
+        }
+    }; // AllValidator
 
-    if (eax_dirty_flags_.lPhase)
-    {
-        set_efx_phase();
-    }
-
-    if (eax_dirty_flags_.flRate)
-    {
-        set_efx_rate();
-    }
-
-    if (eax_dirty_flags_.flDepth)
-    {
-        set_efx_depth();
-    }
-
-    if (eax_dirty_flags_.flFeedback)
-    {
-        set_efx_feedback();
-    }
-
-    if (eax_dirty_flags_.flDelay)
+public:
+    static void SetDefaults(EaxEffectProps &props)
     {
-        set_efx_delay();
+        auto&& all = props.emplace<EaxProps>();
+        all.ulWaveform = Traits::eax_default_waveform();
+        all.lPhase = Traits::eax_default_phase();
+        all.flRate = Traits::eax_default_rate();
+        all.flDepth = Traits::eax_default_depth();
+        all.flFeedback = Traits::eax_default_feedback();
+        all.flDelay = Traits::eax_default_delay();
     }
 
-    eax_dirty_flags_ = EaxChorusEffectDirtyFlags{};
-
-    return true;
-}
 
-void EaxChorusEffect::set(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+    static void Get(const EaxCall &call, const EaxProps &all)
     {
-        case EAXCHORUS_NONE:
+        switch(call.get_property_id())
+        {
+        case Traits::eax_none_param_id():
             break;
-
-        case EAXCHORUS_ALLPARAMETERS:
-            defer_all(eax_call);
+        case Traits::eax_allparameters_param_id():
+            call.template set_value<Exception>(all);
             break;
-
-        case EAXCHORUS_WAVEFORM:
-            defer_waveform(eax_call);
+        case Traits::eax_waveform_param_id():
+            call.template set_value<Exception>(all.ulWaveform);
             break;
-
-        case EAXCHORUS_PHASE:
-            defer_phase(eax_call);
+        case Traits::eax_phase_param_id():
+            call.template set_value<Exception>(all.lPhase);
             break;
-
-        case EAXCHORUS_RATE:
-            defer_rate(eax_call);
+        case Traits::eax_rate_param_id():
+            call.template set_value<Exception>(all.flRate);
             break;
-
-        case EAXCHORUS_DEPTH:
-            defer_depth(eax_call);
+        case Traits::eax_depth_param_id():
+            call.template set_value<Exception>(all.flDepth);
             break;
-
-        case EAXCHORUS_FEEDBACK:
-            defer_feedback(eax_call);
+        case Traits::eax_feedback_param_id():
+            call.template set_value<Exception>(all.flFeedback);
             break;
-
-        case EAXCHORUS_DELAY:
-            defer_delay(eax_call);
+        case Traits::eax_delay_param_id():
+            call.template set_value<Exception>(all.flDelay);
             break;
-
         default:
-            throw EaxChorusEffectException{"Unsupported property id."};
+            Committer::fail_unknown_property_id();
+        }
     }
-}
-
-
-} // namespace
-
-
-EaxEffectUPtr eax_create_eax_chorus_effect()
-{
-    return std::make_unique<::EaxChorusEffect>();
-}
-
-
-namespace
-{
-
-
-using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t;
 
-struct EaxFlangerEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxFlangerEffectDirtyFlagsValue ulWaveform : 1;
-    EaxFlangerEffectDirtyFlagsValue lPhase : 1;
-    EaxFlangerEffectDirtyFlagsValue flRate : 1;
-    EaxFlangerEffectDirtyFlagsValue flDepth : 1;
-    EaxFlangerEffectDirtyFlagsValue flFeedback : 1;
-    EaxFlangerEffectDirtyFlagsValue flDelay : 1;
-}; // EaxFlangerEffectDirtyFlags
-
-
-class EaxFlangerEffect final :
-    public EaxEffect
-{
-public:
-    EaxFlangerEffect();
-
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXFLANGERPROPERTIES eax_{};
-    EAXFLANGERPROPERTIES eax_d_{};
-    EaxFlangerEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_waveform();
-    void set_efx_phase();
-    void set_efx_rate();
-    void set_efx_depth();
-    void set_efx_feedback();
-    void set_efx_delay();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_waveform(unsigned long ulWaveform);
-    void validate_phase(long lPhase);
-    void validate_rate(float flRate);
-    void validate_depth(float flDepth);
-    void validate_feedback(float flFeedback);
-    void validate_delay(float flDelay);
-    void validate_all(const EAXFLANGERPROPERTIES& all);
-
-    void defer_waveform(unsigned long ulWaveform);
-    void defer_phase(long lPhase);
-    void defer_rate(float flRate);
-    void defer_depth(float flDepth);
-    void defer_feedback(float flFeedback);
-    void defer_delay(float flDelay);
-    void defer_all(const EAXFLANGERPROPERTIES& all);
-
-    void defer_waveform(const EaxEaxCall& eax_call);
-    void defer_phase(const EaxEaxCall& eax_call);
-    void defer_rate(const EaxEaxCall& eax_call);
-    void defer_depth(const EaxEaxCall& eax_call);
-    void defer_feedback(const EaxEaxCall& eax_call);
-    void defer_delay(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxFlangerEffect
-
-
-class EaxFlangerEffectException :
-    public EaxException
-{
-public:
-    explicit EaxFlangerEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_FLANGER_EFFECT", message}
+    static void Set(const EaxCall &call, EaxProps &all)
     {
-    }
-}; // EaxFlangerEffectException
-
-
-EaxFlangerEffect::EaxFlangerEffect()
-    : EaxEffect{AL_EFFECT_FLANGER}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxFlangerEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxFlangerEffect::set_eax_defaults()
-{
-    eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM;
-    eax_.lPhase = EAXFLANGER_DEFAULTPHASE;
-    eax_.flRate = EAXFLANGER_DEFAULTRATE;
-    eax_.flDepth = EAXFLANGER_DEFAULTDEPTH;
-    eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK;
-    eax_.flDelay = EAXFLANGER_DEFAULTDELAY;
-
-    eax_d_ = eax_;
-}
-
-void EaxFlangerEffect::set_efx_waveform()
-{
-    const auto waveform = clamp(
-        static_cast<ALint>(eax_.ulWaveform),
-        AL_FLANGER_MIN_WAVEFORM,
-        AL_FLANGER_MAX_WAVEFORM);
-
-    eax_set_efx_waveform(waveform, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_phase()
-{
-    const auto phase = clamp(
-        static_cast<ALint>(eax_.lPhase),
-        AL_FLANGER_MIN_PHASE,
-        AL_FLANGER_MAX_PHASE);
-
-    eax_set_efx_phase(phase, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_rate()
-{
-    const auto rate = clamp(
-        eax_.flRate,
-        AL_FLANGER_MIN_RATE,
-        AL_FLANGER_MAX_RATE);
-
-    eax_set_efx_rate(rate, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_depth()
-{
-    const auto depth = clamp(
-        eax_.flDepth,
-        AL_FLANGER_MIN_DEPTH,
-        AL_FLANGER_MAX_DEPTH);
-
-    eax_set_efx_depth(depth, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_feedback()
-{
-    const auto feedback = clamp(
-        eax_.flFeedback,
-        AL_FLANGER_MIN_FEEDBACK,
-        AL_FLANGER_MAX_FEEDBACK);
-
-    eax_set_efx_feedback(feedback, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_delay()
-{
-    const auto delay = clamp(
-        eax_.flDelay,
-        AL_FLANGER_MIN_DELAY,
-        AL_FLANGER_MAX_DELAY);
-
-    eax_set_efx_delay(delay, al_effect_props_);
-}
-
-void EaxFlangerEffect::set_efx_defaults()
-{
-    set_efx_waveform();
-    set_efx_phase();
-    set_efx_rate();
-    set_efx_depth();
-    set_efx_feedback();
-    set_efx_delay();
-}
-
-void EaxFlangerEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
-    {
-        case EAXFLANGER_NONE:
+        switch(call.get_property_id())
+        {
+        case Traits::eax_none_param_id():
             break;
-
-        case EAXFLANGER_ALLPARAMETERS:
-            eax_call.set_value<EaxFlangerEffectException>(eax_);
+        case Traits::eax_allparameters_param_id():
+            Committer::template defer<AllValidator>(call, all);
             break;
-
-        case EAXFLANGER_WAVEFORM:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.ulWaveform);
+        case Traits::eax_waveform_param_id():
+            Committer::template defer<WaveformValidator>(call, all.ulWaveform);
             break;
-
-        case EAXFLANGER_PHASE:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.lPhase);
+        case Traits::eax_phase_param_id():
+            Committer::template defer<PhaseValidator>(call, all.lPhase);
             break;
-
-        case EAXFLANGER_RATE:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.flRate);
+        case Traits::eax_rate_param_id():
+            Committer::template defer<RateValidator>(call, all.flRate);
             break;
-
-        case EAXFLANGER_DEPTH:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.flDepth);
+        case Traits::eax_depth_param_id():
+            Committer::template defer<DepthValidator>(call, all.flDepth);
             break;
-
-        case EAXFLANGER_FEEDBACK:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.flFeedback);
+        case Traits::eax_feedback_param_id():
+            Committer::template defer<FeedbackValidator>(call, all.flFeedback);
             break;
-
-        case EAXFLANGER_DELAY:
-            eax_call.set_value<EaxFlangerEffectException>(eax_.flDelay);
+        case Traits::eax_delay_param_id():
+            Committer::template defer<DelayValidator>(call, all.flDelay);
             break;
-
         default:
-            throw EaxFlangerEffectException{"Unsupported property id."};
+            Committer::fail_unknown_property_id();
+        }
     }
-}
-
-void EaxFlangerEffect::validate_waveform(
-    unsigned long ulWaveform)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Waveform",
-        ulWaveform,
-        EAXFLANGER_MINWAVEFORM,
-        EAXFLANGER_MAXWAVEFORM);
-}
-
-void EaxFlangerEffect::validate_phase(
-    long lPhase)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Phase",
-        lPhase,
-        EAXFLANGER_MINPHASE,
-        EAXFLANGER_MAXPHASE);
-}
-
-void EaxFlangerEffect::validate_rate(
-    float flRate)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Rate",
-        flRate,
-        EAXFLANGER_MINRATE,
-        EAXFLANGER_MAXRATE);
-}
 
-void EaxFlangerEffect::validate_depth(
-    float flDepth)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Depth",
-        flDepth,
-        EAXFLANGER_MINDEPTH,
-        EAXFLANGER_MAXDEPTH);
-}
-
-void EaxFlangerEffect::validate_feedback(
-    float flFeedback)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Feedback",
-        flFeedback,
-        EAXFLANGER_MINFEEDBACK,
-        EAXFLANGER_MAXFEEDBACK);
-}
-
-void EaxFlangerEffect::validate_delay(
-    float flDelay)
-{
-    eax_validate_range<EaxFlangerEffectException>(
-        "Delay",
-        flDelay,
-        EAXFLANGER_MINDELAY,
-        EAXFLANGER_MAXDELAY);
-}
+    static bool Commit(const EaxProps &props, EaxEffectProps &props_, AlProps &al_props_)
+    {
+        if(auto *cur = std::get_if<EaxProps>(&props_); cur && *cur == props)
+            return false;
 
-void EaxFlangerEffect::validate_all(
-    const EAXFLANGERPROPERTIES& all)
-{
-    validate_waveform(all.ulWaveform);
-    validate_phase(all.lPhase);
-    validate_rate(all.flRate);
-    validate_depth(all.flDepth);
-    validate_feedback(all.flDelay);
-    validate_delay(all.flDelay);
-}
+        props_ = props;
 
-void EaxFlangerEffect::defer_waveform(
-    unsigned long ulWaveform)
-{
-    eax_d_.ulWaveform = ulWaveform;
-    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
-}
+        al_props_.Waveform = Traits::eax_waveform(props.ulWaveform);
+        al_props_.Phase = static_cast<int>(props.lPhase);
+        al_props_.Rate = props.flRate;
+        al_props_.Depth = props.flDepth;
+        al_props_.Feedback = props.flFeedback;
+        al_props_.Delay = props.flDelay;
 
-void EaxFlangerEffect::defer_phase(
-    long lPhase)
-{
-    eax_d_.lPhase = lPhase;
-    eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase);
-}
+        return true;
+    }
+}; // EaxChorusFlangerEffect
 
-void EaxFlangerEffect::defer_rate(
-    float flRate)
-{
-    eax_d_.flRate = flRate;
-    eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
-}
 
-void EaxFlangerEffect::defer_depth(
-    float flDepth)
-{
-    eax_d_.flDepth = flDepth;
-    eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth);
-}
+using ChorusCommitter = EaxCommitter<EaxChorusCommitter>;
+using FlangerCommitter = EaxCommitter<EaxFlangerCommitter>;
 
-void EaxFlangerEffect::defer_feedback(
-    float flFeedback)
-{
-    eax_d_.flFeedback = flFeedback;
-    eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
-}
+} // namespace
 
-void EaxFlangerEffect::defer_delay(
-    float flDelay)
+template<>
+struct ChorusCommitter::Exception : public EaxException
 {
-    eax_d_.flDelay = flDelay;
-    eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
-}
+    explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message}
+    { }
+};
 
-void EaxFlangerEffect::defer_all(
-    const EAXFLANGERPROPERTIES& all)
+template<>
+[[noreturn]] void ChorusCommitter::fail(const char *message)
 {
-    defer_waveform(all.ulWaveform);
-    defer_phase(all.lPhase);
-    defer_rate(all.flRate);
-    defer_depth(all.flDepth);
-    defer_feedback(all.flDelay);
-    defer_delay(all.flDelay);
+    throw Exception{message};
 }
 
-void EaxFlangerEffect::defer_waveform(
-    const EaxEaxCall& eax_call)
+bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props)
 {
-    const auto& waveform =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::ulWaveform)>();
-
-    validate_waveform(waveform);
-    defer_waveform(waveform);
+    using Committer = ChorusFlangerEffect<EaxChorusTraits>;
+    return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
 }
 
-void EaxFlangerEffect::defer_phase(
-    const EaxEaxCall& eax_call)
+void EaxChorusCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& phase =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::lPhase)>();
-
-    validate_phase(phase);
-    defer_phase(phase);
+    using Committer = ChorusFlangerEffect<EaxChorusTraits>;
+    Committer::SetDefaults(props);
 }
 
-void EaxFlangerEffect::defer_rate(
-    const EaxEaxCall& eax_call)
+void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props)
 {
-    const auto& rate =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flRate)>();
-
-    validate_rate(rate);
-    defer_rate(rate);
+    using Committer = ChorusFlangerEffect<EaxChorusTraits>;
+    Committer::Get(call, props);
 }
 
-void EaxFlangerEffect::defer_depth(
-    const EaxEaxCall& eax_call)
+void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props)
 {
-    const auto& depth =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDepth)>();
-
-    validate_depth(depth);
-    defer_depth(depth);
+    using Committer = ChorusFlangerEffect<EaxChorusTraits>;
+    Committer::Set(call, props);
 }
 
-void EaxFlangerEffect::defer_feedback(
-    const EaxEaxCall& eax_call)
+template<>
+struct FlangerCommitter::Exception : public EaxException
 {
-    const auto& feedback =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flFeedback)>();
+    explicit Exception(const char *message) : EaxException{"EAX_FLANGER_EFFECT", message}
+    { }
+};
 
-    validate_feedback(feedback);
-    defer_feedback(feedback);
-}
-
-void EaxFlangerEffect::defer_delay(
-    const EaxEaxCall& eax_call)
+template<>
+[[noreturn]] void FlangerCommitter::fail(const char *message)
 {
-    const auto& delay =
-        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDelay)>();
-
-    validate_delay(delay);
-    defer_delay(delay);
+    throw Exception{message};
 }
 
-void EaxFlangerEffect::defer_all(
-    const EaxEaxCall& eax_call)
+bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props)
 {
-    const auto& all =
-        eax_call.get_value<EaxFlangerEffectException, const EAXFLANGERPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
+    return Committer::Commit(props, mEaxProps, mAlProps.emplace<FlangerProps>());
 }
 
-// [[nodiscard]]
-bool EaxFlangerEffect::apply_deferred()
+void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)
 {
-    if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.ulWaveform)
-    {
-        set_efx_waveform();
-    }
-
-    if (eax_dirty_flags_.lPhase)
-    {
-        set_efx_phase();
-    }
-
-    if (eax_dirty_flags_.flRate)
-    {
-        set_efx_rate();
-    }
-
-    if (eax_dirty_flags_.flDepth)
-    {
-        set_efx_depth();
-    }
-
-    if (eax_dirty_flags_.flFeedback)
-    {
-        set_efx_feedback();
-    }
-
-    if (eax_dirty_flags_.flDelay)
-    {
-        set_efx_delay();
-    }
-
-    eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{};
-
-    return true;
+    using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
+    Committer::SetDefaults(props);
 }
 
-void EaxFlangerEffect::set(const EaxEaxCall& eax_call)
+void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
-    {
-        case EAXFLANGER_NONE:
-            break;
-
-        case EAXFLANGER_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXFLANGER_WAVEFORM:
-            defer_waveform(eax_call);
-            break;
-
-        case EAXFLANGER_PHASE:
-            defer_phase(eax_call);
-            break;
-
-        case EAXFLANGER_RATE:
-            defer_rate(eax_call);
-            break;
-
-        case EAXFLANGER_DEPTH:
-            defer_depth(eax_call);
-            break;
-
-        case EAXFLANGER_FEEDBACK:
-            defer_feedback(eax_call);
-            break;
-
-        case EAXFLANGER_DELAY:
-            defer_delay(eax_call);
-            break;
-
-        default:
-            throw EaxFlangerEffectException{"Unsupported property id."};
-    }
+    using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
+    Committer::Get(call, props);
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_flanger_effect()
+void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props)
 {
-    return std::make_unique<EaxFlangerEffect>();
+    using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
+    Committer::Set(call, props);
 }
 
 #endif // ALSOFT_EAX

+ 68 - 208
libs/openal-soft/al/effects/compressor.cpp

@@ -9,22 +9,33 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Compressor_setParami(EffectProps *props, ALenum param, int val)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    CompressorProps props{};
+    props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
+    return props;
+}
+
+} // namespace
+
+const EffectProps CompressorEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(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"};
-        props->Compressor.OnOff = (val != AL_FALSE);
+        props.OnOff = (val != AL_FALSE);
         break;
 
     default:
@@ -32,22 +43,22 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Compressor_setParami(props, param, vals[0]); }
-void Compressor_setParamf(EffectProps*, ALenum param, float)
+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 Compressor_setParamfv(EffectProps*, ALenum param, const float*)
+void EffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
         param};
 }
 
-void Compressor_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val)
 { 
     switch(param)
     {
     case AL_COMPRESSOR_ONOFF:
-        *val = props->Compressor.OnOff;
+        *val = props.OnOff;
         break;
 
     default:
@@ -55,242 +66,91 @@ void Compressor_getParami(const EffectProps *props, ALenum param, int *val)
             param};
     }
 }
-void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Compressor_getParami(props, param, vals); }
-void Compressor_getParamf(const EffectProps*, ALenum param, float*)
+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 Compressor_getParamfv(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
         param};
 }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Compressor);
-
-const EffectProps CompressorEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t;
+using CompressorCommitter = EaxCommitter<EaxCompressorCommitter>;
 
-struct EaxCompressorEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxCompressorEffectDirtyFlagsValue ulOnOff : 1;
-}; // EaxCompressorEffectDirtyFlags
-
-
-class EaxCompressorEffect final :
-    public EaxEffect
-{
-public:
-    EaxCompressorEffect();
-
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXAGCCOMPRESSORPROPERTIES eax_{};
-    EAXAGCCOMPRESSORPROPERTIES eax_d_{};
-    EaxCompressorEffectDirtyFlags eax_dirty_flags_{};
-
-
-    void set_eax_defaults();
-
-    void set_efx_on_off();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_on_off(unsigned long ulOnOff);
-    void validate_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all);
-
-    void defer_on_off(unsigned long ulOnOff);
-    void defer_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all);
-
-    void defer_on_off(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxCompressorEffect
-
-
-class EaxCompressorEffectException :
-    public EaxException
-{
-public:
-    explicit EaxCompressorEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_COMPRESSOR_EFFECT", message}
+struct OnOffValidator {
+    void operator()(unsigned long ulOnOff) const
     {
+        eax_validate_range<CompressorCommitter::Exception>(
+            "On-Off",
+            ulOnOff,
+            EAXAGCCOMPRESSOR_MINONOFF,
+            EAXAGCCOMPRESSOR_MAXONOFF);
     }
-}; // EaxCompressorEffectException
-
+}; // OnOffValidator
 
-EaxCompressorEffect::EaxCompressorEffect()
-    : EaxEffect{AL_EFFECT_COMPRESSOR}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-// [[nodiscard]]
-void EaxCompressorEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxCompressorEffect::set_eax_defaults()
-{
-    eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF;
-
-    eax_d_ = eax_;
-}
-
-void EaxCompressorEffect::set_efx_on_off()
-{
-    const auto on_off = clamp(
-        static_cast<ALint>(eax_.ulOnOff),
-        AL_COMPRESSOR_MIN_ONOFF,
-        AL_COMPRESSOR_MAX_ONOFF);
-
-    al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE);
-}
-
-void EaxCompressorEffect::set_efx_defaults()
-{
-    set_efx_on_off();
-}
-
-void EaxCompressorEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct AllValidator {
+    void operator()(const EAXAGCCOMPRESSORPROPERTIES& all) const
     {
-        case EAXAGCCOMPRESSOR_NONE:
-            break;
-
-        case EAXAGCCOMPRESSOR_ALLPARAMETERS:
-            eax_call.set_value<EaxCompressorEffectException>(eax_);
-            break;
-
-        case EAXAGCCOMPRESSOR_ONOFF:
-            eax_call.set_value<EaxCompressorEffectException>(eax_.ulOnOff);
-            break;
-
-        default:
-            throw EaxCompressorEffectException{"Unsupported property id."};
+        OnOffValidator{}(all.ulOnOff);
     }
-}
+}; // AllValidator
 
-void EaxCompressorEffect::validate_on_off(
-    unsigned long ulOnOff)
-{
-    eax_validate_range<EaxCompressorEffectException>(
-        "On-Off",
-        ulOnOff,
-        EAXAGCCOMPRESSOR_MINONOFF,
-        EAXAGCCOMPRESSOR_MAXONOFF);
-}
+} // namespace
 
-void EaxCompressorEffect::validate_all(
-    const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+template<>
+struct CompressorCommitter::Exception : public EaxException
 {
-    validate_on_off(eax_all.ulOnOff);
-}
+    explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message}
+    { }
+};
 
-void EaxCompressorEffect::defer_on_off(
-    unsigned long ulOnOff)
+template<>
+[[noreturn]] void CompressorCommitter::fail(const char *message)
 {
-    eax_d_.ulOnOff = ulOnOff;
-    eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff);
+    throw Exception{message};
 }
 
-void EaxCompressorEffect::defer_all(
-    const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props)
 {
-    defer_on_off(eax_all.ulOnOff);
-}
+    if(auto *cur = std::get_if<EAXAGCCOMPRESSORPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-void EaxCompressorEffect::defer_on_off(
-    const EaxEaxCall& eax_call)
-{
-    const auto& on_off =
-        eax_call.get_value<EaxCompressorEffectException, const decltype(EAXAGCCOMPRESSORPROPERTIES::ulOnOff)>();
+    mEaxProps = props;
+    mAlProps = CompressorProps{props.ulOnOff != 0};
 
-    validate_on_off(on_off);
-    defer_on_off(on_off);
+    return true;
 }
 
-void EaxCompressorEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& all =
-        eax_call.get_value<EaxCompressorEffectException, const EAXAGCCOMPRESSORPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF};
 }
 
-// [[nodiscard]]
-bool EaxCompressorEffect::apply_deferred()
+void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props)
 {
-    if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{})
+    switch(call.get_property_id())
     {
-        return false;
+    case EAXAGCCOMPRESSOR_NONE: break;
+    case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.ulOnOff); break;
+    default: fail_unknown_property_id();
     }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.ulOnOff)
-    {
-        set_efx_on_off();
-    }
-
-    eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxCompressorEffect::set(const EaxEaxCall& eax_call)
+void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXAGCCOMPRESSOR_NONE:
-            break;
-
-        case EAXAGCCOMPRESSOR_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXAGCCOMPRESSOR_ONOFF:
-            defer_on_off(eax_call);
-            break;
-
-        default:
-            throw EaxCompressorEffectException{"Unsupported property id."};
+    case EAXAGCCOMPRESSOR_NONE: break;
+    case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.ulOnOff); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_compressor_effect()
-{
-    return std::make_unique<EaxCompressorEffect>();
-}
-
 #endif // ALSOFT_EAX

+ 54 - 30
libs/openal-soft/al/effects/convolution.cpp

@@ -1,93 +1,117 @@
 
 #include "config.h"
 
+#include <algorithm>
+#include <array>
+#include <cmath>
+
 #include "AL/al.h"
-#include "alc/inprogext.h"
 
-#include "alc/effects/base.h"
+#include "alc/inprogext.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/effects/base.h"
 #include "effects.h"
 
 
 namespace {
 
-void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    ConvolutionProps props{};
+    props.OrientAt = {0.0f,  0.0f, -1.0f};
+    props.OrientUp = {0.0f,  1.0f,  0.0f};
+    return props;
+}
+
+} // namespace
+
+const EffectProps ConvolutionEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
             param};
     }
 }
-void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals)
+void EffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals)
 {
     switch(param)
     {
     default:
-        Convolution_setParami(props, param, vals[0]);
+        SetParami(props, param, *vals);
     }
 }
-void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
+void EffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
             param};
     }
 }
-void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals)
+void EffectHandler::SetParamfv(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};
+        if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker))
+            throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value 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:
-        Convolution_setParamf(props, param, vals[0]);
+        SetParamf(props, param, *values);
     }
 }
 
-void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
+void EffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
             param};
     }
 }
-void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals)
+void EffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals)
 {
     switch(param)
     {
     default:
-        Convolution_getParami(props, param, vals);
+        GetParami(props, param, vals);
     }
 }
-void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
+void EffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
             param};
     }
 }
-void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals)
+void EffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values)
 {
+    al::span<float> vals;
     switch(param)
     {
+    case AL_CONVOLUTION_ORIENTATION_SOFT:
+        vals = {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:
-        Convolution_getParamf(props, param, vals);
+        GetParamf(props, param, values);
     }
 }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Convolution);
-
-const EffectProps ConvolutionEffectProps{genDefaultProps()};

+ 70 - 22
libs/openal-soft/al/effects/dedicated.cpp

@@ -12,61 +12,109 @@
 
 namespace {
 
-void Dedicated_setParami(EffectProps*, ALenum param, int)
+constexpr EffectProps genDefaultDialogProps() noexcept
+{
+    DedicatedDialogProps props{};
+    props.Gain = 1.0f;
+    return props;
+}
+
+constexpr EffectProps genDefaultLfeProps() noexcept
+{
+    DedicatedLfeProps props{};
+    props.Gain = 1.0f;
+    return props;
+}
+
+} // namespace
+
+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 Dedicated_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(DedicatedDialogProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void Dedicated_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(DedicatedDialogProps &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"};
-        props->Dedicated.Gain = val;
+        props.Gain = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
     }
 }
-void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Dedicated_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(DedicatedDialogProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Dedicated_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const DedicatedDialogProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
-void Dedicated_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const DedicatedDialogProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const DedicatedDialogProps &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};
+    }
+}
+void EffectHandler::GetParamfv(const DedicatedDialogProps &props, ALenum param, float *vals)
+{ GetParamf(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)
 {
     switch(param)
     {
     case AL_DEDICATED_GAIN:
-        *val = props->Dedicated.Gain;
+        if(!(val >= 0.0f && std::isfinite(val)))
+            throw effect_exception{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};
     }
 }
-void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Dedicated_getParamf(props, param, vals); }
+void EffectHandler::SetParamfv(DedicatedLfeProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-EffectProps genDefaultProps() noexcept
+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*)
 {
-    EffectProps props{};
-    props.Dedicated.Gain = 1.0f;
-    return props;
+    throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
+        param};
 }
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Dedicated);
-
-const EffectProps DedicatedEffectProps{genDefaultProps()};
+void EffectHandler::GetParamf(const DedicatedLfeProps &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};
+    }
+}
+void EffectHandler::GetParamfv(const DedicatedLfeProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }

+ 150 - 464
libs/openal-soft/al/effects/distortion.cpp

@@ -9,563 +9,249 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Distortion_setParami(EffectProps*, ALenum param, int)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    DistortionProps props{};
+    props.Edge = AL_DISTORTION_DEFAULT_EDGE;
+    props.Gain = AL_DISTORTION_DEFAULT_GAIN;
+    props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
+    props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
+    props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
+    return props;
+}
+
+} // namespace
+
+const EffectProps DistortionEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(DistortionProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
-void Distortion_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
         param};
 }
-void Distortion_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(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"};
-        props->Distortion.Edge = val;
+        props.Edge = val;
         break;
 
     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"};
-        props->Distortion.Gain = val;
+        props.Gain = val;
         break;
 
     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"};
-        props->Distortion.LowpassCutoff = val;
+        props.LowpassCutoff = val;
         break;
 
     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"};
-        props->Distortion.EQCenter = val;
+        props.EQCenter = val;
         break;
 
     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"};
-        props->Distortion.EQBandwidth = val;
+        props.EQBandwidth = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
     }
 }
-void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Distortion_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Distortion_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const DistortionProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
-void Distortion_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
         param};
 }
-void Distortion_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_DISTORTION_EDGE:
-        *val = props->Distortion.Edge;
-        break;
-
-    case AL_DISTORTION_GAIN:
-        *val = props->Distortion.Gain;
-        break;
-
-    case AL_DISTORTION_LOWPASS_CUTOFF:
-        *val = props->Distortion.LowpassCutoff;
-        break;
-
-    case AL_DISTORTION_EQCENTER:
-        *val = props->Distortion.EQCenter;
-        break;
-
-    case AL_DISTORTION_EQBANDWIDTH:
-        *val = props->Distortion.EQBandwidth;
-        break;
+    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};
     }
 }
-void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Distortion_getParamf(props, param, vals); }
+void EffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
-    props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
-    props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
-    props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
-    props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Distortion);
-
-const EffectProps DistortionEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxDistortionEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxDistortionEffectDirtyFlagsValue flEdge : 1;
-    EaxDistortionEffectDirtyFlagsValue lGain : 1;
-    EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1;
-    EaxDistortionEffectDirtyFlagsValue flEQCenter : 1;
-    EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1;
-}; // EaxDistortionEffectDirtyFlags
-
-
-class EaxDistortionEffect final :
-    public EaxEffect
-{
-public:
-    EaxDistortionEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXDISTORTIONPROPERTIES eax_{};
-    EAXDISTORTIONPROPERTIES eax_d_{};
-    EaxDistortionEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_edge();
-    void set_efx_gain();
-    void set_efx_lowpass_cutoff();
-    void set_efx_eq_center();
-    void set_efx_eq_bandwidth();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_edge(float flEdge);
-    void validate_gain(long lGain);
-    void validate_lowpass_cutoff(float flLowPassCutOff);
-    void validate_eq_center(float flEQCenter);
-    void validate_eq_bandwidth(float flEQBandwidth);
-    void validate_all(const EAXDISTORTIONPROPERTIES& eax_all);
-
-    void defer_edge(float flEdge);
-    void defer_gain(long lGain);
-    void defer_low_pass_cutoff(float flLowPassCutOff);
-    void defer_eq_center(float flEQCenter);
-    void defer_eq_bandwidth(float flEQBandwidth);
-    void defer_all(const EAXDISTORTIONPROPERTIES& eax_all);
+using DistortionCommitter = EaxCommitter<EaxDistortionCommitter>;
 
-    void defer_edge(const EaxEaxCall& eax_call);
-    void defer_gain(const EaxEaxCall& eax_call);
-    void defer_low_pass_cutoff(const EaxEaxCall& eax_call);
-    void defer_eq_center(const EaxEaxCall& eax_call);
-    void defer_eq_bandwidth(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxDistortionEffect
-
-
-class EaxDistortionEffectException :
-    public EaxException
-{
-public:
-    explicit EaxDistortionEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_DISTORTION_EFFECT", message}
+struct EdgeValidator {
+    void operator()(float flEdge) const
     {
+        eax_validate_range<DistortionCommitter::Exception>(
+            "Edge",
+            flEdge,
+            EAXDISTORTION_MINEDGE,
+            EAXDISTORTION_MAXEDGE);
     }
-}; // EaxDistortionEffectException
-
-
-EaxDistortionEffect::EaxDistortionEffect()
-    : EaxEffect{AL_EFFECT_DISTORTION}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxDistortionEffect::set_eax_defaults()
-{
-    eax_.flEdge = EAXDISTORTION_DEFAULTEDGE;
-    eax_.lGain = EAXDISTORTION_DEFAULTGAIN;
-    eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
-    eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
-    eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
-
-    eax_d_ = eax_;
-}
-
-void EaxDistortionEffect::set_efx_edge()
-{
-    const auto edge = clamp(
-        eax_.flEdge,
-        AL_DISTORTION_MIN_EDGE,
-        AL_DISTORTION_MAX_EDGE);
-
-    al_effect_props_.Distortion.Edge = edge;
-}
-
-void EaxDistortionEffect::set_efx_gain()
-{
-    const auto gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lGain)),
-        AL_DISTORTION_MIN_GAIN,
-        AL_DISTORTION_MAX_GAIN);
-
-    al_effect_props_.Distortion.Gain = gain;
-}
+}; // EdgeValidator
 
-void EaxDistortionEffect::set_efx_lowpass_cutoff()
-{
-    const auto lowpass_cutoff = clamp(
-        eax_.flLowPassCutOff,
-        AL_DISTORTION_MIN_LOWPASS_CUTOFF,
-        AL_DISTORTION_MAX_LOWPASS_CUTOFF);
-
-    al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff;
-}
-
-void EaxDistortionEffect::set_efx_eq_center()
-{
-    const auto eq_center = clamp(
-        eax_.flEQCenter,
-        AL_DISTORTION_MIN_EQCENTER,
-        AL_DISTORTION_MAX_EQCENTER);
-
-    al_effect_props_.Distortion.EQCenter = eq_center;
-}
-
-void EaxDistortionEffect::set_efx_eq_bandwidth()
-{
-    const auto eq_bandwidth = clamp(
-        eax_.flEdge,
-        AL_DISTORTION_MIN_EQBANDWIDTH,
-        AL_DISTORTION_MAX_EQBANDWIDTH);
-
-    al_effect_props_.Distortion.EQBandwidth = eq_bandwidth;
-}
-
-void EaxDistortionEffect::set_efx_defaults()
-{
-    set_efx_edge();
-    set_efx_gain();
-    set_efx_lowpass_cutoff();
-    set_efx_eq_center();
-    set_efx_eq_bandwidth();
-}
-
-void EaxDistortionEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct GainValidator {
+    void operator()(long lGain) const
     {
-        case EAXDISTORTION_NONE:
-            break;
-
-        case EAXDISTORTION_ALLPARAMETERS:
-            eax_call.set_value<EaxDistortionEffectException>(eax_);
-            break;
-
-        case EAXDISTORTION_EDGE:
-            eax_call.set_value<EaxDistortionEffectException>(eax_.flEdge);
-            break;
-
-        case EAXDISTORTION_GAIN:
-            eax_call.set_value<EaxDistortionEffectException>(eax_.lGain);
-            break;
-
-        case EAXDISTORTION_LOWPASSCUTOFF:
-            eax_call.set_value<EaxDistortionEffectException>(eax_.flLowPassCutOff);
-            break;
-
-        case EAXDISTORTION_EQCENTER:
-            eax_call.set_value<EaxDistortionEffectException>(eax_.flEQCenter);
-            break;
-
-        case EAXDISTORTION_EQBANDWIDTH:
-            eax_call.set_value<EaxDistortionEffectException>(eax_.flEQBandwidth);
-            break;
-
-        default:
-            throw EaxDistortionEffectException{"Unsupported property id."};
+        eax_validate_range<DistortionCommitter::Exception>(
+            "Gain",
+            lGain,
+            EAXDISTORTION_MINGAIN,
+            EAXDISTORTION_MAXGAIN);
     }
-}
-
-void EaxDistortionEffect::validate_edge(
-    float flEdge)
-{
-    eax_validate_range<EaxDistortionEffectException>(
-        "Edge",
-        flEdge,
-        EAXDISTORTION_MINEDGE,
-        EAXDISTORTION_MAXEDGE);
-}
-
-void EaxDistortionEffect::validate_gain(
-    long lGain)
-{
-    eax_validate_range<EaxDistortionEffectException>(
-        "Gain",
-        lGain,
-        EAXDISTORTION_MINGAIN,
-        EAXDISTORTION_MAXGAIN);
-}
-
-void EaxDistortionEffect::validate_lowpass_cutoff(
-    float flLowPassCutOff)
-{
-    eax_validate_range<EaxDistortionEffectException>(
-        "Low-pass Cut-off",
-        flLowPassCutOff,
-        EAXDISTORTION_MINLOWPASSCUTOFF,
-        EAXDISTORTION_MAXLOWPASSCUTOFF);
-}
-
-void EaxDistortionEffect::validate_eq_center(
-    float flEQCenter)
-{
-    eax_validate_range<EaxDistortionEffectException>(
-        "EQ Center",
-        flEQCenter,
-        EAXDISTORTION_MINEQCENTER,
-        EAXDISTORTION_MAXEQCENTER);
-}
-
-void EaxDistortionEffect::validate_eq_bandwidth(
-    float flEQBandwidth)
-{
-    eax_validate_range<EaxDistortionEffectException>(
-        "EQ Bandwidth",
-        flEQBandwidth,
-        EAXDISTORTION_MINEQBANDWIDTH,
-        EAXDISTORTION_MAXEQBANDWIDTH);
-}
-
-void EaxDistortionEffect::validate_all(
-    const EAXDISTORTIONPROPERTIES& eax_all)
-{
-    validate_edge(eax_all.flEdge);
-    validate_gain(eax_all.lGain);
-    validate_lowpass_cutoff(eax_all.flLowPassCutOff);
-    validate_eq_center(eax_all.flEQCenter);
-    validate_eq_bandwidth(eax_all.flEQBandwidth);
-}
-
-void EaxDistortionEffect::defer_edge(
-    float flEdge)
-{
-    eax_d_.flEdge = flEdge;
-    eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge);
-}
+}; // GainValidator
 
-void EaxDistortionEffect::defer_gain(
-    long lGain)
-{
-    eax_d_.lGain = lGain;
-    eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain);
-}
-
-void EaxDistortionEffect::defer_low_pass_cutoff(
-    float flLowPassCutOff)
-{
-    eax_d_.flLowPassCutOff = flLowPassCutOff;
-    eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff);
-}
-
-void EaxDistortionEffect::defer_eq_center(
-    float flEQCenter)
-{
-    eax_d_.flEQCenter = flEQCenter;
-    eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter);
-}
-
-void EaxDistortionEffect::defer_eq_bandwidth(
-    float flEQBandwidth)
-{
-    eax_d_.flEQBandwidth = flEQBandwidth;
-    eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth);
-}
-
-void EaxDistortionEffect::defer_all(
-    const EAXDISTORTIONPROPERTIES& eax_all)
-{
-    defer_edge(eax_all.flEdge);
-    defer_gain(eax_all.lGain);
-    defer_low_pass_cutoff(eax_all.flLowPassCutOff);
-    defer_eq_center(eax_all.flEQCenter);
-    defer_eq_bandwidth(eax_all.flEQBandwidth);
-}
-
-void EaxDistortionEffect::defer_edge(
-    const EaxEaxCall& eax_call)
-{
-    const auto& edge =
-        eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEdge)>();
-
-    validate_edge(edge);
-    defer_edge(edge);
-}
-
-void EaxDistortionEffect::defer_gain(
-    const EaxEaxCall& eax_call)
-{
-    const auto& gain =
-        eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::lGain)>();
-
-    validate_gain(gain);
-    defer_gain(gain);
-}
-
-void EaxDistortionEffect::defer_low_pass_cutoff(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lowpass_cutoff =
-        eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flLowPassCutOff)>();
-
-    validate_lowpass_cutoff(lowpass_cutoff);
-    defer_low_pass_cutoff(lowpass_cutoff);
-}
-
-void EaxDistortionEffect::defer_eq_center(
-    const EaxEaxCall& eax_call)
-{
-    const auto& eq_center =
-        eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQCenter)>();
-
-    validate_eq_center(eq_center);
-    defer_eq_center(eq_center);
-}
-
-void EaxDistortionEffect::defer_eq_bandwidth(
-    const EaxEaxCall& eax_call)
-{
-    const auto& eq_bandwidth =
-        eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQBandwidth)>();
-
-    validate_eq_bandwidth(eq_bandwidth);
-    defer_eq_bandwidth(eq_bandwidth);
-}
-
-void EaxDistortionEffect::defer_all(
-    const EaxEaxCall& eax_call)
-{
-    const auto& all =
-        eax_call.get_value<EaxDistortionEffectException, const EAXDISTORTIONPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
-}
-
-// [[nodiscard]]
-bool EaxDistortionEffect::apply_deferred()
-{
-    if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.flEdge)
+struct LowPassCutOffValidator {
+    void operator()(float flLowPassCutOff) const
     {
-        set_efx_edge();
+        eax_validate_range<DistortionCommitter::Exception>(
+            "Low-pass Cut-off",
+            flLowPassCutOff,
+            EAXDISTORTION_MINLOWPASSCUTOFF,
+            EAXDISTORTION_MAXLOWPASSCUTOFF);
     }
+}; // LowPassCutOffValidator
 
-    if (eax_dirty_flags_.lGain)
+struct EqCenterValidator {
+    void operator()(float flEQCenter) const
     {
-        set_efx_gain();
+        eax_validate_range<DistortionCommitter::Exception>(
+            "EQ Center",
+            flEQCenter,
+            EAXDISTORTION_MINEQCENTER,
+            EAXDISTORTION_MAXEQCENTER);
     }
+}; // EqCenterValidator
 
-    if (eax_dirty_flags_.flLowPassCutOff)
+struct EqBandwidthValidator {
+    void operator()(float flEQBandwidth) const
     {
-        set_efx_lowpass_cutoff();
+        eax_validate_range<DistortionCommitter::Exception>(
+            "EQ Bandwidth",
+            flEQBandwidth,
+            EAXDISTORTION_MINEQBANDWIDTH,
+            EAXDISTORTION_MAXEQBANDWIDTH);
     }
+}; // EqBandwidthValidator
 
-    if (eax_dirty_flags_.flEQCenter)
+struct AllValidator {
+    void operator()(const EAXDISTORTIONPROPERTIES& all) const
     {
-        set_efx_eq_center();
+        EdgeValidator{}(all.flEdge);
+        GainValidator{}(all.lGain);
+        LowPassCutOffValidator{}(all.flLowPassCutOff);
+        EqCenterValidator{}(all.flEQCenter);
+        EqBandwidthValidator{}(all.flEQBandwidth);
     }
+}; // AllValidator
 
-    if (eax_dirty_flags_.flEQBandwidth)
-    {
-        set_efx_eq_bandwidth();
-    }
+} // namespace
 
-    eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{};
+template<>
+struct DistortionCommitter::Exception : public EaxException {
+    explicit Exception(const char *message) : EaxException{"EAX_DISTORTION_EFFECT", message}
+    { }
+};
 
-    return true;
+template<>
+[[noreturn]] void DistortionCommitter::fail(const char *message)
+{
+    throw Exception{message};
 }
 
-void EaxDistortionEffect::set(const EaxEaxCall& eax_call)
+bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
-    {
-        case EAXDISTORTION_NONE:
-            break;
-
-        case EAXDISTORTION_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXDISTORTION_EDGE:
-            defer_edge(eax_call);
-            break;
-
-        case EAXDISTORTION_GAIN:
-            defer_gain(eax_call);
-            break;
+    if(auto *cur = std::get_if<EAXDISTORTIONPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-        case EAXDISTORTION_LOWPASSCUTOFF:
-            defer_low_pass_cutoff(eax_call);
-            break;
+    mEaxProps = props;
+    mAlProps = [&]{
+        DistortionProps ret{};
+        ret.Edge = props.flEdge;
+        ret.Gain = level_mb_to_gain(static_cast<float>(props.lGain));
+        ret.LowpassCutoff = props.flLowPassCutOff;
+        ret.EQCenter = props.flEQCenter;
+        ret.EQBandwidth = props.flEdge;
+        return ret;
+    }();
 
-        case EAXDISTORTION_EQCENTER:
-            defer_eq_center(eax_call);
-            break;
+    return true;
+}
 
-        case EAXDISTORTION_EQBANDWIDTH:
-            defer_eq_bandwidth(eax_call);
-            break;
+void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props)
+{
+    static constexpr EAXDISTORTIONPROPERTIES defprops{[]
+    {
+        EAXDISTORTIONPROPERTIES ret{};
+        ret.flEdge = EAXDISTORTION_DEFAULTEDGE;
+        ret.lGain = EAXDISTORTION_DEFAULTGAIN;
+        ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
+        ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
+        ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
+        return ret;
+    }()};
+    props = defprops;
+}
 
-        default:
-            throw EaxDistortionEffectException{"Unsupported property id."};
+void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props)
+{
+    switch(call.get_property_id())
+    {
+    case EAXDISTORTION_NONE: break;
+    case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXDISTORTION_EDGE: call.set_value<Exception>(props.flEdge); break;
+    case EAXDISTORTION_GAIN: call.set_value<Exception>(props.lGain); break;
+    case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.flLowPassCutOff); break;
+    case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.flEQCenter); break;
+    case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.flEQBandwidth); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_distortion_effect()
+void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props)
 {
-    return std::make_unique<EaxDistortionEffect>();
+    switch(call.get_property_id())
+    {
+    case EAXDISTORTION_NONE: break;
+    case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.flEdge); break;
+    case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.lGain); break;
+    case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.flLowPassCutOff); break;
+    case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.flEQCenter); break;
+    case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.flEQBandwidth); break;
+    default: fail_unknown_property_id();
+    }
 }
 
 #endif // ALSOFT_EAX

+ 150 - 465
libs/openal-soft/al/effects/echo.cpp

@@ -9,9 +9,9 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
@@ -20,550 +20,235 @@ namespace {
 static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short");
 static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short");
 
-void Echo_setParami(EffectProps*, ALenum param, int)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    EchoProps props{};
+    props.Delay    = AL_ECHO_DEFAULT_DELAY;
+    props.LRDelay  = AL_ECHO_DEFAULT_LRDELAY;
+    props.Damping  = AL_ECHO_DEFAULT_DAMPING;
+    props.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
+    props.Spread   = AL_ECHO_DEFAULT_SPREAD;
+    return props;
+}
+
+} // namespace
+
+const EffectProps EchoEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(EchoProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
-void Echo_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(EchoProps&, ALenum param, const int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
-void Echo_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(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"};
-        props->Echo.Delay = val;
+        props.Delay = val;
         break;
 
     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"};
-        props->Echo.LRDelay = val;
+        props.LRDelay = val;
         break;
 
     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"};
-        props->Echo.Damping = val;
+        props.Damping = val;
         break;
 
     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"};
-        props->Echo.Feedback = val;
+        props.Feedback = val;
         break;
 
     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"};
-        props->Echo.Spread = val;
+        props.Spread = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
     }
 }
-void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Echo_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Echo_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const EchoProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
-void Echo_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const EchoProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
-void Echo_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_ECHO_DELAY:
-        *val = props->Echo.Delay;
-        break;
-
-    case AL_ECHO_LRDELAY:
-        *val = props->Echo.LRDelay;
-        break;
-
-    case AL_ECHO_DAMPING:
-        *val = props->Echo.Damping;
-        break;
-
-    case AL_ECHO_FEEDBACK:
-        *val = props->Echo.Feedback;
-        break;
-
-    case AL_ECHO_SPREAD:
-        *val = props->Echo.Spread;
-        break;
+    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};
     }
 }
-void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Echo_getParamf(props, param, vals); }
+void EffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Echo.Delay    = AL_ECHO_DEFAULT_DELAY;
-    props.Echo.LRDelay  = AL_ECHO_DEFAULT_LRDELAY;
-    props.Echo.Damping  = AL_ECHO_DEFAULT_DAMPING;
-    props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
-    props.Echo.Spread   = AL_ECHO_DEFAULT_SPREAD;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Echo);
-
-const EffectProps EchoEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxEchoEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxEchoEffectDirtyFlagsValue flDelay : 1;
-    EaxEchoEffectDirtyFlagsValue flLRDelay : 1;
-    EaxEchoEffectDirtyFlagsValue flDamping : 1;
-    EaxEchoEffectDirtyFlagsValue flFeedback : 1;
-    EaxEchoEffectDirtyFlagsValue flSpread : 1;
-}; // EaxEchoEffectDirtyFlags
-
-
-class EaxEchoEffect final :
-    public EaxEffect
-{
-public:
-    EaxEchoEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXECHOPROPERTIES eax_{};
-    EAXECHOPROPERTIES eax_d_{};
-    EaxEchoEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_delay();
-    void set_efx_lr_delay();
-    void set_efx_damping();
-    void set_efx_feedback();
-    void set_efx_spread();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_delay(float flDelay);
-    void validate_lr_delay(float flLRDelay);
-    void validate_damping(float flDamping);
-    void validate_feedback(float flFeedback);
-    void validate_spread(float flSpread);
-    void validate_all(const EAXECHOPROPERTIES& all);
-
-    void defer_delay(float flDelay);
-    void defer_lr_delay(float flLRDelay);
-    void defer_damping(float flDamping);
-    void defer_feedback(float flFeedback);
-    void defer_spread(float flSpread);
-    void defer_all(const EAXECHOPROPERTIES& all);
+using EchoCommitter = EaxCommitter<EaxEchoCommitter>;
 
-    void defer_delay(const EaxEaxCall& eax_call);
-    void defer_lr_delay(const EaxEaxCall& eax_call);
-    void defer_damping(const EaxEaxCall& eax_call);
-    void defer_feedback(const EaxEaxCall& eax_call);
-    void defer_spread(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxEchoEffect
-
-
-class EaxEchoEffectException :
-    public EaxException
-{
-public:
-    explicit EaxEchoEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_ECHO_EFFECT", message}
+struct DelayValidator {
+    void operator()(float flDelay) const
     {
+        eax_validate_range<EchoCommitter::Exception>(
+            "Delay",
+            flDelay,
+            EAXECHO_MINDELAY,
+            EAXECHO_MAXDELAY);
     }
-}; // EaxEchoEffectException
-
-
-EaxEchoEffect::EaxEchoEffect()
-    : EaxEffect{AL_EFFECT_ECHO}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxEchoEffect::dispatch(
-    const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxEchoEffect::set_eax_defaults()
-{
-    eax_.flDelay = EAXECHO_DEFAULTDELAY;
-    eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY;
-    eax_.flDamping = EAXECHO_DEFAULTDAMPING;
-    eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK;
-    eax_.flSpread = EAXECHO_DEFAULTSPREAD;
-
-    eax_d_ = eax_;
-}
-
-void EaxEchoEffect::set_efx_delay()
-{
-    const auto delay = clamp(
-        eax_.flDelay,
-        AL_ECHO_MIN_DELAY,
-        AL_ECHO_MAX_DELAY);
-
-    al_effect_props_.Echo.Delay = delay;
-}
-
-void EaxEchoEffect::set_efx_lr_delay()
-{
-    const auto lr_delay = clamp(
-        eax_.flLRDelay,
-        AL_ECHO_MIN_LRDELAY,
-        AL_ECHO_MAX_LRDELAY);
-
-    al_effect_props_.Echo.LRDelay = lr_delay;
-}
+}; // DelayValidator
 
-void EaxEchoEffect::set_efx_damping()
-{
-    const auto damping = clamp(
-        eax_.flDamping,
-        AL_ECHO_MIN_DAMPING,
-        AL_ECHO_MAX_DAMPING);
-
-    al_effect_props_.Echo.Damping = damping;
-}
-
-void EaxEchoEffect::set_efx_feedback()
-{
-    const auto feedback = clamp(
-        eax_.flFeedback,
-        AL_ECHO_MIN_FEEDBACK,
-        AL_ECHO_MAX_FEEDBACK);
-
-    al_effect_props_.Echo.Feedback = feedback;
-}
-
-void EaxEchoEffect::set_efx_spread()
-{
-    const auto spread = clamp(
-        eax_.flSpread,
-        AL_ECHO_MIN_SPREAD,
-        AL_ECHO_MAX_SPREAD);
-
-    al_effect_props_.Echo.Spread = spread;
-}
-
-void EaxEchoEffect::set_efx_defaults()
-{
-    set_efx_delay();
-    set_efx_lr_delay();
-    set_efx_damping();
-    set_efx_feedback();
-    set_efx_spread();
-}
-
-void EaxEchoEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct LrDelayValidator {
+    void operator()(float flLRDelay) const
     {
-        case EAXECHO_NONE:
-            break;
-
-        case EAXECHO_ALLPARAMETERS:
-            eax_call.set_value<EaxEchoEffectException>(eax_);
-            break;
-
-        case EAXECHO_DELAY:
-            eax_call.set_value<EaxEchoEffectException>(eax_.flDelay);
-            break;
-
-        case EAXECHO_LRDELAY:
-            eax_call.set_value<EaxEchoEffectException>(eax_.flLRDelay);
-            break;
-
-        case EAXECHO_DAMPING:
-            eax_call.set_value<EaxEchoEffectException>(eax_.flDamping);
-            break;
-
-        case EAXECHO_FEEDBACK:
-            eax_call.set_value<EaxEchoEffectException>(eax_.flFeedback);
-            break;
-
-        case EAXECHO_SPREAD:
-            eax_call.set_value<EaxEchoEffectException>(eax_.flSpread);
-            break;
-
-        default:
-            throw EaxEchoEffectException{"Unsupported property id."};
+        eax_validate_range<EchoCommitter::Exception>(
+            "LR Delay",
+            flLRDelay,
+            EAXECHO_MINLRDELAY,
+            EAXECHO_MAXLRDELAY);
     }
-}
-
-void EaxEchoEffect::validate_delay(
-    float flDelay)
-{
-    eax_validate_range<EaxEchoEffectException>(
-        "Delay",
-        flDelay,
-        EAXECHO_MINDELAY,
-        EAXECHO_MAXDELAY);
-}
-
-void EaxEchoEffect::validate_lr_delay(
-    float flLRDelay)
-{
-    eax_validate_range<EaxEchoEffectException>(
-        "LR Delay",
-        flLRDelay,
-        EAXECHO_MINLRDELAY,
-        EAXECHO_MAXLRDELAY);
-}
-
-void EaxEchoEffect::validate_damping(
-    float flDamping)
-{
-    eax_validate_range<EaxEchoEffectException>(
-        "Damping",
-        flDamping,
-        EAXECHO_MINDAMPING,
-        EAXECHO_MAXDAMPING);
-}
-
-void EaxEchoEffect::validate_feedback(
-    float flFeedback)
-{
-    eax_validate_range<EaxEchoEffectException>(
-        "Feedback",
-        flFeedback,
-        EAXECHO_MINFEEDBACK,
-        EAXECHO_MAXFEEDBACK);
-}
-
-void EaxEchoEffect::validate_spread(
-    float flSpread)
-{
-    eax_validate_range<EaxEchoEffectException>(
-        "Spread",
-        flSpread,
-        EAXECHO_MINSPREAD,
-        EAXECHO_MAXSPREAD);
-}
-
-void EaxEchoEffect::validate_all(
-    const EAXECHOPROPERTIES& all)
-{
-    validate_delay(all.flDelay);
-    validate_lr_delay(all.flLRDelay);
-    validate_damping(all.flDamping);
-    validate_feedback(all.flFeedback);
-    validate_spread(all.flSpread);
-}
-
-void EaxEchoEffect::defer_delay(
-    float flDelay)
-{
-    eax_d_.flDelay = flDelay;
-    eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
-}
+}; // LrDelayValidator
 
-void EaxEchoEffect::defer_lr_delay(
-    float flLRDelay)
-{
-    eax_d_.flLRDelay = flLRDelay;
-    eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay);
-}
-
-void EaxEchoEffect::defer_damping(
-    float flDamping)
-{
-    eax_d_.flDamping = flDamping;
-    eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping);
-}
-
-void EaxEchoEffect::defer_feedback(
-    float flFeedback)
-{
-    eax_d_.flFeedback = flFeedback;
-    eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
-}
-
-void EaxEchoEffect::defer_spread(
-    float flSpread)
-{
-    eax_d_.flSpread = flSpread;
-    eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread);
-}
-
-void EaxEchoEffect::defer_all(
-    const EAXECHOPROPERTIES& all)
-{
-    defer_delay(all.flDelay);
-    defer_lr_delay(all.flLRDelay);
-    defer_damping(all.flDamping);
-    defer_feedback(all.flFeedback);
-    defer_spread(all.flSpread);
-}
-
-void EaxEchoEffect::defer_delay(
-    const EaxEaxCall& eax_call)
-{
-    const auto& delay =
-        eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDelay)>();
-
-    validate_delay(delay);
-    defer_delay(delay);
-}
-
-void EaxEchoEffect::defer_lr_delay(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lr_delay =
-        eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flLRDelay)>();
-
-    validate_lr_delay(lr_delay);
-    defer_lr_delay(lr_delay);
-}
-
-void EaxEchoEffect::defer_damping(
-    const EaxEaxCall& eax_call)
-{
-    const auto& damping =
-        eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDamping)>();
-
-    validate_damping(damping);
-    defer_damping(damping);
-}
-
-void EaxEchoEffect::defer_feedback(
-    const EaxEaxCall& eax_call)
-{
-    const auto& feedback =
-        eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flFeedback)>();
-
-    validate_feedback(feedback);
-    defer_feedback(feedback);
-}
-
-void EaxEchoEffect::defer_spread(
-    const EaxEaxCall& eax_call)
-{
-    const auto& spread =
-        eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flSpread)>();
-
-    validate_spread(spread);
-    defer_spread(spread);
-}
-
-void EaxEchoEffect::defer_all(
-    const EaxEaxCall& eax_call)
-{
-    const auto& all =
-        eax_call.get_value<EaxEchoEffectException, const EAXECHOPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
-}
-
-// [[nodiscard]]
-bool EaxEchoEffect::apply_deferred()
-{
-    if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.flDelay)
+struct DampingValidator {
+    void operator()(float flDamping) const
     {
-        set_efx_delay();
+        eax_validate_range<EchoCommitter::Exception>(
+            "Damping",
+            flDamping,
+            EAXECHO_MINDAMPING,
+            EAXECHO_MAXDAMPING);
     }
+}; // DampingValidator
 
-    if (eax_dirty_flags_.flLRDelay)
+struct FeedbackValidator {
+    void operator()(float flFeedback) const
     {
-        set_efx_lr_delay();
+        eax_validate_range<EchoCommitter::Exception>(
+            "Feedback",
+            flFeedback,
+            EAXECHO_MINFEEDBACK,
+            EAXECHO_MAXFEEDBACK);
     }
+}; // FeedbackValidator
 
-    if (eax_dirty_flags_.flDamping)
+struct SpreadValidator {
+    void operator()(float flSpread) const
     {
-        set_efx_damping();
+        eax_validate_range<EchoCommitter::Exception>(
+            "Spread",
+            flSpread,
+            EAXECHO_MINSPREAD,
+            EAXECHO_MAXSPREAD);
     }
+}; // SpreadValidator
 
-    if (eax_dirty_flags_.flFeedback)
+struct AllValidator {
+    void operator()(const EAXECHOPROPERTIES& all) const
     {
-        set_efx_feedback();
+        DelayValidator{}(all.flDelay);
+        LrDelayValidator{}(all.flLRDelay);
+        DampingValidator{}(all.flDamping);
+        FeedbackValidator{}(all.flFeedback);
+        SpreadValidator{}(all.flSpread);
     }
+}; // AllValidator
 
-    if (eax_dirty_flags_.flSpread)
-    {
-        set_efx_spread();
-    }
+} // namespace
 
-    eax_dirty_flags_ = EaxEchoEffectDirtyFlags{};
+template<>
+struct EchoCommitter::Exception : public EaxException {
+    explicit Exception(const char* message) : EaxException{"EAX_ECHO_EFFECT", message}
+    { }
+};
 
-    return true;
+template<>
+[[noreturn]] void EchoCommitter::fail(const char *message)
+{
+    throw Exception{message};
 }
 
-void EaxEchoEffect::set(const EaxEaxCall& eax_call)
+bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
-    {
-        case EAXECHO_NONE:
-            break;
-
-        case EAXECHO_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXECHO_DELAY:
-            defer_delay(eax_call);
-            break;
-
-        case EAXECHO_LRDELAY:
-            defer_lr_delay(eax_call);
-            break;
+    if(auto *cur = std::get_if<EAXECHOPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-        case EAXECHO_DAMPING:
-            defer_damping(eax_call);
-            break;
+    mEaxProps = props;
+    mAlProps = [&]{
+        EchoProps ret{};
+        ret.Delay = props.flDelay;
+        ret.LRDelay = props.flLRDelay;
+        ret.Damping = props.flDamping;
+        ret.Feedback = props.flFeedback;
+        ret.Spread = props.flSpread;
+        return ret;
+    }();
 
-        case EAXECHO_FEEDBACK:
-            defer_feedback(eax_call);
-            break;
+    return true;
+}
 
-        case EAXECHO_SPREAD:
-            defer_spread(eax_call);
-            break;
+void EaxEchoCommitter::SetDefaults(EaxEffectProps &props)
+{
+    static constexpr EAXECHOPROPERTIES defprops{[]
+    {
+        EAXECHOPROPERTIES ret{};
+        ret.flDelay = EAXECHO_DEFAULTDELAY;
+        ret.flLRDelay = EAXECHO_DEFAULTLRDELAY;
+        ret.flDamping = EAXECHO_DEFAULTDAMPING;
+        ret.flFeedback = EAXECHO_DEFAULTFEEDBACK;
+        ret.flSpread = EAXECHO_DEFAULTSPREAD;
+        return ret;
+    }()};
+    props = defprops;
+}
 
-        default:
-            throw EaxEchoEffectException{"Unsupported property id."};
+void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props)
+{
+    switch(call.get_property_id())
+    {
+    case EAXECHO_NONE: break;
+    case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXECHO_DELAY: call.set_value<Exception>(props.flDelay); break;
+    case EAXECHO_LRDELAY: call.set_value<Exception>(props.flLRDelay); break;
+    case EAXECHO_DAMPING: call.set_value<Exception>(props.flDamping); break;
+    case EAXECHO_FEEDBACK: call.set_value<Exception>(props.flFeedback); break;
+    case EAXECHO_SPREAD: call.set_value<Exception>(props.flSpread); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_echo_effect()
+void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props)
 {
-    return std::make_unique<EaxEchoEffect>();
+    switch(call.get_property_id())
+    {
+    case EAXECHO_NONE: break;
+    case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXECHO_DELAY: defer<DelayValidator>(call, props.flDelay); break;
+    case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.flLRDelay); break;
+    case EAXECHO_DAMPING: defer<DampingValidator>(call, props.flDamping); break;
+    case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.flFeedback); break;
+    case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.flSpread); break;
+    default: fail_unknown_property_id();
+    }
 }
 
 #endif // ALSOFT_EAX

+ 0 - 63
libs/openal-soft/al/effects/effects.cpp

@@ -1,66 +1,3 @@
-
 #include "config.h"
 
-#ifdef ALSOFT_EAX
-
 #include "effects.h"
-
-#include <cassert>
-
-#include "AL/efx.h"
-
-
-EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type)
-{
-#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] "
-
-    switch (al_effect_type)
-    {
-        case AL_EFFECT_NULL:
-            return eax_create_eax_null_effect();
-
-        case AL_EFFECT_CHORUS:
-            return eax_create_eax_chorus_effect();
-
-        case AL_EFFECT_DISTORTION:
-            return eax_create_eax_distortion_effect();
-
-        case AL_EFFECT_ECHO:
-            return eax_create_eax_echo_effect();
-
-        case AL_EFFECT_FLANGER:
-            return eax_create_eax_flanger_effect();
-
-        case AL_EFFECT_FREQUENCY_SHIFTER:
-            return eax_create_eax_frequency_shifter_effect();
-
-        case AL_EFFECT_VOCAL_MORPHER:
-            return eax_create_eax_vocal_morpher_effect();
-
-        case AL_EFFECT_PITCH_SHIFTER:
-            return eax_create_eax_pitch_shifter_effect();
-
-        case AL_EFFECT_RING_MODULATOR:
-            return eax_create_eax_ring_modulator_effect();
-
-        case AL_EFFECT_AUTOWAH:
-            return eax_create_eax_auto_wah_effect();
-
-        case AL_EFFECT_COMPRESSOR:
-            return eax_create_eax_compressor_effect();
-
-        case AL_EFFECT_EQUALIZER:
-            return eax_create_eax_equalizer_effect();
-
-        case AL_EFFECT_EAXREVERB:
-            return eax_create_eax_reverb_effect();
-
-        default:
-            assert(false && "Unsupported AL effect type.");
-            return nullptr;
-    }
-
-#undef EAX_PREFIX
-}
-
-#endif // ALSOFT_EAX

+ 46 - 65
libs/openal-soft/al/effects/effects.h

@@ -1,51 +1,54 @@
 #ifndef AL_EFFECTS_EFFECTS_H
 #define AL_EFFECTS_EFFECTS_H
 
-#include "AL/al.h"
-
-#include "core/except.h"
-
-#ifdef ALSOFT_EAX
-#include "al/eax_effect.h"
-#endif // ALSOFT_EAX
-
-union EffectProps;
-
-
-class effect_exception final : public al::base_exception {
-    ALenum mErrorCode;
-
-public:
-#ifdef __USE_MINGW_ANSI_STDIO
-    [[gnu::format(gnu_printf, 3, 4)]]
-#else
-    [[gnu::format(printf, 3, 4)]]
-#endif
-    effect_exception(ALenum code, const char *msg, ...);
+#include <variant>
 
-    ALenum errorCode() const noexcept { return mErrorCode; }
-};
-
-
-struct EffectVtable {
-    void (*const setParami)(EffectProps *props, ALenum param, int val);
-    void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals);
-    void (*const setParamf)(EffectProps *props, ALenum param, float val);
-    void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals);
+#include "AL/al.h"
 
-    void (*const getParami)(const EffectProps *props, ALenum param, int *val);
-    void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals);
-    void (*const getParamf)(const EffectProps *props, ALenum param, float *val);
-    void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals);
+#include "al/error.h"
+#include "core/effects/base.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 DEFINE_ALEFFECT_VTABLE(T)           \
-const EffectVtable T##EffectVtable = {      \
-    T##_setParami, T##_setParamiv,          \
-    T##_setParamf, T##_setParamfv,          \
-    T##_getParami, T##_getParamiv,          \
-    T##_getParamf, T##_getParamfv,          \
-}
+using effect_exception = al::context_error;
 
 
 /* Default properties for the given effect types. */
@@ -63,30 +66,8 @@ extern const EffectProps FshifterEffectProps;
 extern const EffectProps ModulatorEffectProps;
 extern const EffectProps PshifterEffectProps;
 extern const EffectProps VmorpherEffectProps;
-extern const EffectProps DedicatedEffectProps;
+extern const EffectProps DedicatedDialogEffectProps;
+extern const EffectProps DedicatedLfeEffectProps;
 extern const EffectProps ConvolutionEffectProps;
 
-/* Vtables to get/set properties for the given effect types. */
-extern const EffectVtable NullEffectVtable;
-extern const EffectVtable ReverbEffectVtable;
-extern const EffectVtable StdReverbEffectVtable;
-extern const EffectVtable AutowahEffectVtable;
-extern const EffectVtable ChorusEffectVtable;
-extern const EffectVtable CompressorEffectVtable;
-extern const EffectVtable DistortionEffectVtable;
-extern const EffectVtable EchoEffectVtable;
-extern const EffectVtable EqualizerEffectVtable;
-extern const EffectVtable FlangerEffectVtable;
-extern const EffectVtable FshifterEffectVtable;
-extern const EffectVtable ModulatorEffectVtable;
-extern const EffectVtable PshifterEffectVtable;
-extern const EffectVtable VmorpherEffectVtable;
-extern const EffectVtable DedicatedEffectVtable;
-extern const EffectVtable ConvolutionEffectVtable;
-
-
-#ifdef ALSOFT_EAX
-EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type);
-#endif // ALSOFT_EAX
-
 #endif /* AL_EFFECTS_EFFECTS_H */

+ 231 - 775
libs/openal-soft/al/effects/equalizer.cpp

@@ -9,913 +9,369 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Equalizer_setParami(EffectProps*, ALenum param, int)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    EqualizerProps props{};
+    props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
+    props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
+    props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
+    props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
+    props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
+    props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
+    props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
+    props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
+    props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
+    props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
+    return props;
+}
+
+} // namespace
+
+const EffectProps EqualizerEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(EqualizerProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
-void Equalizer_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
         param};
 }
-void Equalizer_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(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"};
-        props->Equalizer.LowGain = val;
+        props.LowGain = val;
         break;
 
     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"};
-        props->Equalizer.LowCutoff = val;
+        props.LowCutoff = val;
         break;
 
     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"};
-        props->Equalizer.Mid1Gain = val;
+        props.Mid1Gain = val;
         break;
 
     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"};
-        props->Equalizer.Mid1Center = val;
+        props.Mid1Center = val;
         break;
 
     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"};
-        props->Equalizer.Mid1Width = val;
+        props.Mid1Width = val;
         break;
 
     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"};
-        props->Equalizer.Mid2Gain = val;
+        props.Mid2Gain = val;
         break;
 
     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"};
-        props->Equalizer.Mid2Center = val;
+        props.Mid2Center = val;
         break;
 
     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"};
-        props->Equalizer.Mid2Width = val;
+        props.Mid2Width = val;
         break;
 
     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"};
-        props->Equalizer.HighGain = val;
+        props.HighGain = val;
         break;
 
     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"};
-        props->Equalizer.HighCutoff = val;
+        props.HighCutoff = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
     }
 }
-void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Equalizer_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Equalizer_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const EqualizerProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
-void Equalizer_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
         param};
 }
-void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_EQUALIZER_LOW_GAIN:
-        *val = props->Equalizer.LowGain;
-        break;
-
-    case AL_EQUALIZER_LOW_CUTOFF:
-        *val = props->Equalizer.LowCutoff;
-        break;
-
-    case AL_EQUALIZER_MID1_GAIN:
-        *val = props->Equalizer.Mid1Gain;
-        break;
-
-    case AL_EQUALIZER_MID1_CENTER:
-        *val = props->Equalizer.Mid1Center;
-        break;
-
-    case AL_EQUALIZER_MID1_WIDTH:
-        *val = props->Equalizer.Mid1Width;
-        break;
-
-    case AL_EQUALIZER_MID2_GAIN:
-        *val = props->Equalizer.Mid2Gain;
-        break;
-
-    case AL_EQUALIZER_MID2_CENTER:
-        *val = props->Equalizer.Mid2Center;
-        break;
-
-    case AL_EQUALIZER_MID2_WIDTH:
-        *val = props->Equalizer.Mid2Width;
-        break;
-
-    case AL_EQUALIZER_HIGH_GAIN:
-        *val = props->Equalizer.HighGain;
-        break;
-
-    case AL_EQUALIZER_HIGH_CUTOFF:
-        *val = props->Equalizer.HighCutoff;
-        break;
+    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};
     }
 }
-void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Equalizer_getParamf(props, param, vals); }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
-    props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
-    props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
-    props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
-    props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
-    props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
-    props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
-    props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
-    props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
-    props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Equalizer);
+void EffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-const EffectProps EqualizerEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t;
-
-struct EaxEqualizerEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxEqualizerEffectDirtyFlagsValue lLowGain : 1;
-    EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1;
-    EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1;
-    EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1;
-    EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1;
-    EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1;
-    EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1;
-    EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1;
-    EaxEqualizerEffectDirtyFlagsValue lHighGain : 1;
-    EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1;
-}; // EaxEqualizerEffectDirtyFlags
-
-
-class EaxEqualizerEffect final :
-    public EaxEffect
-{
-public:
-    EaxEqualizerEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXEQUALIZERPROPERTIES eax_{};
-    EAXEQUALIZERPROPERTIES eax_d_{};
-    EaxEqualizerEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_low_gain();
-    void set_efx_low_cutoff();
-    void set_efx_mid1_gain();
-    void set_efx_mid1_center();
-    void set_efx_mid1_width();
-    void set_efx_mid2_gain();
-    void set_efx_mid2_center();
-    void set_efx_mid2_width();
-    void set_efx_high_gain();
-    void set_efx_high_cutoff();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_low_gain(long lLowGain);
-    void validate_low_cutoff(float flLowCutOff);
-    void validate_mid1_gain(long lMid1Gain);
-    void validate_mid1_center(float flMid1Center);
-    void validate_mid1_width(float flMid1Width);
-    void validate_mid2_gain(long lMid2Gain);
-    void validate_mid2_center(float flMid2Center);
-    void validate_mid2_width(float flMid2Width);
-    void validate_high_gain(long lHighGain);
-    void validate_high_cutoff(float flHighCutOff);
-    void validate_all(const EAXEQUALIZERPROPERTIES& all);
-
-    void defer_low_gain(long lLowGain);
-    void defer_low_cutoff(float flLowCutOff);
-    void defer_mid1_gain(long lMid1Gain);
-    void defer_mid1_center(float flMid1Center);
-    void defer_mid1_width(float flMid1Width);
-    void defer_mid2_gain(long lMid2Gain);
-    void defer_mid2_center(float flMid2Center);
-    void defer_mid2_width(float flMid2Width);
-    void defer_high_gain(long lHighGain);
-    void defer_high_cutoff(float flHighCutOff);
-    void defer_all(const EAXEQUALIZERPROPERTIES& all);
-
-    void defer_low_gain(const EaxEaxCall& eax_call);
-    void defer_low_cutoff(const EaxEaxCall& eax_call);
-    void defer_mid1_gain(const EaxEaxCall& eax_call);
-    void defer_mid1_center(const EaxEaxCall& eax_call);
-    void defer_mid1_width(const EaxEaxCall& eax_call);
-    void defer_mid2_gain(const EaxEaxCall& eax_call);
-    void defer_mid2_center(const EaxEaxCall& eax_call);
-    void defer_mid2_width(const EaxEaxCall& eax_call);
-    void defer_high_gain(const EaxEaxCall& eax_call);
-    void defer_high_cutoff(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxEqualizerEffect
-
-
-class EaxEqualizerEffectException :
-    public EaxException
-{
-public:
-    explicit EaxEqualizerEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_EQUALIZER_EFFECT", message}
-    {
-    }
-}; // EaxEqualizerEffectException
-
-
-EaxEqualizerEffect::EaxEqualizerEffect()
-    : EaxEffect{AL_EFFECT_EQUALIZER}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxEqualizerEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxEqualizerEffect::set_eax_defaults()
-{
-    eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
-    eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
-    eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
-    eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
-    eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
-    eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
-    eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
-    eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
-    eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
-    eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
-
-    eax_d_ = eax_;
-}
-
-void EaxEqualizerEffect::set_efx_low_gain()
-{
-    const auto low_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lLowGain)),
-        AL_EQUALIZER_MIN_LOW_GAIN,
-        AL_EQUALIZER_MAX_LOW_GAIN);
-
-    al_effect_props_.Equalizer.LowGain = low_gain;
-}
-
-void EaxEqualizerEffect::set_efx_low_cutoff()
-{
-    const auto low_cutoff = clamp(
-        eax_.flLowCutOff,
-        AL_EQUALIZER_MIN_LOW_CUTOFF,
-        AL_EQUALIZER_MAX_LOW_CUTOFF);
-
-    al_effect_props_.Equalizer.LowCutoff = low_cutoff;
-}
-
-void EaxEqualizerEffect::set_efx_mid1_gain()
-{
-    const auto mid1_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lMid1Gain)),
-        AL_EQUALIZER_MIN_MID1_GAIN,
-        AL_EQUALIZER_MAX_MID1_GAIN);
-
-    al_effect_props_.Equalizer.Mid1Gain = mid1_gain;
-}
-
-void EaxEqualizerEffect::set_efx_mid1_center()
-{
-    const auto mid1_center = clamp(
-        eax_.flMid1Center,
-        AL_EQUALIZER_MIN_MID1_CENTER,
-        AL_EQUALIZER_MAX_MID1_CENTER);
-
-    al_effect_props_.Equalizer.Mid1Center = mid1_center;
-}
-
-void EaxEqualizerEffect::set_efx_mid1_width()
-{
-    const auto mid1_width = clamp(
-        eax_.flMid1Width,
-        AL_EQUALIZER_MIN_MID1_WIDTH,
-        AL_EQUALIZER_MAX_MID1_WIDTH);
-
-    al_effect_props_.Equalizer.Mid1Width = mid1_width;
-}
-
-void EaxEqualizerEffect::set_efx_mid2_gain()
-{
-    const auto mid2_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lMid2Gain)),
-        AL_EQUALIZER_MIN_MID2_GAIN,
-        AL_EQUALIZER_MAX_MID2_GAIN);
-
-    al_effect_props_.Equalizer.Mid2Gain = mid2_gain;
-}
-
-void EaxEqualizerEffect::set_efx_mid2_center()
-{
-    const auto mid2_center = clamp(
-        eax_.flMid2Center,
-        AL_EQUALIZER_MIN_MID2_CENTER,
-        AL_EQUALIZER_MAX_MID2_CENTER);
-
-    al_effect_props_.Equalizer.Mid2Center = mid2_center;
-}
-
-void EaxEqualizerEffect::set_efx_mid2_width()
-{
-    const auto mid2_width = clamp(
-        eax_.flMid2Width,
-        AL_EQUALIZER_MIN_MID2_WIDTH,
-        AL_EQUALIZER_MAX_MID2_WIDTH);
-
-    al_effect_props_.Equalizer.Mid2Width = mid2_width;
-}
-
-void EaxEqualizerEffect::set_efx_high_gain()
-{
-    const auto high_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lHighGain)),
-        AL_EQUALIZER_MIN_HIGH_GAIN,
-        AL_EQUALIZER_MAX_HIGH_GAIN);
-
-    al_effect_props_.Equalizer.HighGain = high_gain;
-}
-
-void EaxEqualizerEffect::set_efx_high_cutoff()
-{
-    const auto high_cutoff = clamp(
-        eax_.flHighCutOff,
-        AL_EQUALIZER_MIN_HIGH_CUTOFF,
-        AL_EQUALIZER_MAX_HIGH_CUTOFF);
-
-    al_effect_props_.Equalizer.HighCutoff = high_cutoff;
-}
-
-void EaxEqualizerEffect::set_efx_defaults()
-{
-    set_efx_low_gain();
-    set_efx_low_cutoff();
-    set_efx_mid1_gain();
-    set_efx_mid1_center();
-    set_efx_mid1_width();
-    set_efx_mid2_gain();
-    set_efx_mid2_center();
-    set_efx_mid2_width();
-    set_efx_high_gain();
-    set_efx_high_cutoff();
-}
+using EqualizerCommitter = EaxCommitter<EaxEqualizerCommitter>;
 
-void EaxEqualizerEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct LowGainValidator {
+    void operator()(long lLowGain) const
     {
-        case EAXEQUALIZER_NONE:
-            break;
-
-        case EAXEQUALIZER_ALLPARAMETERS:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_);
-            break;
-
-        case EAXEQUALIZER_LOWGAIN:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.lLowGain);
-            break;
-
-        case EAXEQUALIZER_LOWCUTOFF:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flLowCutOff);
-            break;
-
-        case EAXEQUALIZER_MID1GAIN:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid1Gain);
-            break;
-
-        case EAXEQUALIZER_MID1CENTER:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Center);
-            break;
-
-        case EAXEQUALIZER_MID1WIDTH:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Width);
-            break;
-
-        case EAXEQUALIZER_MID2GAIN:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid2Gain);
-            break;
-
-        case EAXEQUALIZER_MID2CENTER:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Center);
-            break;
-
-        case EAXEQUALIZER_MID2WIDTH:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Width);
-            break;
-
-        case EAXEQUALIZER_HIGHGAIN:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.lHighGain);
-            break;
-
-        case EAXEQUALIZER_HIGHCUTOFF:
-            eax_call.set_value<EaxEqualizerEffectException>(eax_.flHighCutOff);
-            break;
-
-        default:
-            throw EaxEqualizerEffectException{"Unsupported property id."};
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Low Gain",
+            lLowGain,
+            EAXEQUALIZER_MINLOWGAIN,
+            EAXEQUALIZER_MAXLOWGAIN);
     }
-}
-
-void EaxEqualizerEffect::validate_low_gain(
-    long lLowGain)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Low Gain",
-        lLowGain,
-        EAXEQUALIZER_MINLOWGAIN,
-        EAXEQUALIZER_MAXLOWGAIN);
-}
-
-void EaxEqualizerEffect::validate_low_cutoff(
-    float flLowCutOff)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Low Cutoff",
-        flLowCutOff,
-        EAXEQUALIZER_MINLOWCUTOFF,
-        EAXEQUALIZER_MAXLOWCUTOFF);
-}
-
-void EaxEqualizerEffect::validate_mid1_gain(
-    long lMid1Gain)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid1 Gain",
-        lMid1Gain,
-        EAXEQUALIZER_MINMID1GAIN,
-        EAXEQUALIZER_MAXMID1GAIN);
-}
-
-void EaxEqualizerEffect::validate_mid1_center(
-    float flMid1Center)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid1 Center",
-        flMid1Center,
-        EAXEQUALIZER_MINMID1CENTER,
-        EAXEQUALIZER_MAXMID1CENTER);
-}
-
-void EaxEqualizerEffect::validate_mid1_width(
-    float flMid1Width)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid1 Width",
-        flMid1Width,
-        EAXEQUALIZER_MINMID1WIDTH,
-        EAXEQUALIZER_MAXMID1WIDTH);
-}
-
-void EaxEqualizerEffect::validate_mid2_gain(
-    long lMid2Gain)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid2 Gain",
-        lMid2Gain,
-        EAXEQUALIZER_MINMID2GAIN,
-        EAXEQUALIZER_MAXMID2GAIN);
-}
-
-void EaxEqualizerEffect::validate_mid2_center(
-    float flMid2Center)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid2 Center",
-        flMid2Center,
-        EAXEQUALIZER_MINMID2CENTER,
-        EAXEQUALIZER_MAXMID2CENTER);
-}
-
-void EaxEqualizerEffect::validate_mid2_width(
-    float flMid2Width)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "Mid2 Width",
-        flMid2Width,
-        EAXEQUALIZER_MINMID2WIDTH,
-        EAXEQUALIZER_MAXMID2WIDTH);
-}
-
-void EaxEqualizerEffect::validate_high_gain(
-    long lHighGain)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "High Gain",
-        lHighGain,
-        EAXEQUALIZER_MINHIGHGAIN,
-        EAXEQUALIZER_MAXHIGHGAIN);
-}
-
-void EaxEqualizerEffect::validate_high_cutoff(
-    float flHighCutOff)
-{
-    eax_validate_range<EaxEqualizerEffectException>(
-        "High Cutoff",
-        flHighCutOff,
-        EAXEQUALIZER_MINHIGHCUTOFF,
-        EAXEQUALIZER_MAXHIGHCUTOFF);
-}
-
-void EaxEqualizerEffect::validate_all(
-    const EAXEQUALIZERPROPERTIES& all)
-{
-    validate_low_gain(all.lLowGain);
-    validate_low_cutoff(all.flLowCutOff);
-    validate_mid1_gain(all.lMid1Gain);
-    validate_mid1_center(all.flMid1Center);
-    validate_mid1_width(all.flMid1Width);
-    validate_mid2_gain(all.lMid2Gain);
-    validate_mid2_center(all.flMid2Center);
-    validate_mid2_width(all.flMid2Width);
-    validate_high_gain(all.lHighGain);
-    validate_high_cutoff(all.flHighCutOff);
-}
-
-void EaxEqualizerEffect::defer_low_gain(
-    long lLowGain)
-{
-    eax_d_.lLowGain = lLowGain;
-    eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain);
-}
-
-void EaxEqualizerEffect::defer_low_cutoff(
-    float flLowCutOff)
-{
-    eax_d_.flLowCutOff = flLowCutOff;
-    eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff);
-}
-
-void EaxEqualizerEffect::defer_mid1_gain(
-    long lMid1Gain)
-{
-    eax_d_.lMid1Gain = lMid1Gain;
-    eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain);
-}
-
-void EaxEqualizerEffect::defer_mid1_center(
-    float flMid1Center)
-{
-    eax_d_.flMid1Center = flMid1Center;
-    eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center);
-}
-
-void EaxEqualizerEffect::defer_mid1_width(
-    float flMid1Width)
-{
-    eax_d_.flMid1Width = flMid1Width;
-    eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width);
-}
-
-void EaxEqualizerEffect::defer_mid2_gain(
-    long lMid2Gain)
-{
-    eax_d_.lMid2Gain = lMid2Gain;
-    eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain);
-}
-
-void EaxEqualizerEffect::defer_mid2_center(
-    float flMid2Center)
-{
-    eax_d_.flMid2Center = flMid2Center;
-    eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center);
-}
-
-void EaxEqualizerEffect::defer_mid2_width(
-    float flMid2Width)
-{
-    eax_d_.flMid2Width = flMid2Width;
-    eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width);
-}
-
-void EaxEqualizerEffect::defer_high_gain(
-    long lHighGain)
-{
-    eax_d_.lHighGain = lHighGain;
-    eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain);
-}
-
-void EaxEqualizerEffect::defer_high_cutoff(
-    float flHighCutOff)
-{
-    eax_d_.flHighCutOff = flHighCutOff;
-    eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff);
-}
-
-void EaxEqualizerEffect::defer_all(
-    const EAXEQUALIZERPROPERTIES& all)
-{
-    defer_low_gain(all.lLowGain);
-    defer_low_cutoff(all.flLowCutOff);
-    defer_mid1_gain(all.lMid1Gain);
-    defer_mid1_center(all.flMid1Center);
-    defer_mid1_width(all.flMid1Width);
-    defer_mid2_gain(all.lMid2Gain);
-    defer_mid2_center(all.flMid2Center);
-    defer_mid2_width(all.flMid2Width);
-    defer_high_gain(all.lHighGain);
-    defer_high_cutoff(all.flHighCutOff);
-}
-
-void EaxEqualizerEffect::defer_low_gain(
-    const EaxEaxCall& eax_call)
-{
-    const auto& low_gain =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lLowGain)>();
-
-    validate_low_gain(low_gain);
-    defer_low_gain(low_gain);
-}
-
-void EaxEqualizerEffect::defer_low_cutoff(
-    const EaxEaxCall& eax_call)
-{
-    const auto& low_cutoff =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flLowCutOff)>();
-
-    validate_low_cutoff(low_cutoff);
-    defer_low_cutoff(low_cutoff);
-}
+}; // LowGainValidator
 
-void EaxEqualizerEffect::defer_mid1_gain(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid1_gain =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid1Gain)>();
-
-    validate_mid1_gain(mid1_gain);
-    defer_mid1_gain(mid1_gain);
-}
-
-void EaxEqualizerEffect::defer_mid1_center(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid1_center =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Center)>();
-
-    validate_mid1_center(mid1_center);
-    defer_mid1_center(mid1_center);
-}
-
-void EaxEqualizerEffect::defer_mid1_width(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid1_width =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Width)>();
-
-    validate_mid1_width(mid1_width);
-    defer_mid1_width(mid1_width);
-}
-
-void EaxEqualizerEffect::defer_mid2_gain(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid2_gain =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid2Gain)>();
-
-    validate_mid2_gain(mid2_gain);
-    defer_mid2_gain(mid2_gain);
-}
-
-void EaxEqualizerEffect::defer_mid2_center(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid2_center =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Center)>();
-
-    validate_mid2_center(mid2_center);
-    defer_mid2_center(mid2_center);
-}
-
-void EaxEqualizerEffect::defer_mid2_width(
-    const EaxEaxCall& eax_call)
-{
-    const auto& mid2_width =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Width)>();
-
-    validate_mid2_width(mid2_width);
-    defer_mid2_width(mid2_width);
-}
-
-void EaxEqualizerEffect::defer_high_gain(
-    const EaxEaxCall& eax_call)
-{
-    const auto& high_gain =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lHighGain)>();
-
-    validate_high_gain(high_gain);
-    defer_high_gain(high_gain);
-}
-
-void EaxEqualizerEffect::defer_high_cutoff(
-    const EaxEaxCall& eax_call)
-{
-    const auto& high_cutoff =
-        eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flHighCutOff)>();
-
-    validate_high_cutoff(high_cutoff);
-    defer_high_cutoff(high_cutoff);
-}
-
-void EaxEqualizerEffect::defer_all(
-    const EaxEaxCall& eax_call)
-{
-    const auto& all =
-        eax_call.get_value<EaxEqualizerEffectException, const EAXEQUALIZERPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
-}
-
-// [[nodiscard]]
-bool EaxEqualizerEffect::apply_deferred()
-{
-    if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{})
+struct LowCutOffValidator {
+    void operator()(float flLowCutOff) const
     {
-        return false;
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Low Cutoff",
+            flLowCutOff,
+            EAXEQUALIZER_MINLOWCUTOFF,
+            EAXEQUALIZER_MAXLOWCUTOFF);
     }
+}; // LowCutOffValidator
 
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.lLowGain)
+struct Mid1GainValidator {
+    void operator()(long lMid1Gain) const
     {
-        set_efx_low_gain();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid1 Gain",
+            lMid1Gain,
+            EAXEQUALIZER_MINMID1GAIN,
+            EAXEQUALIZER_MAXMID1GAIN);
     }
+}; // Mid1GainValidator
 
-    if (eax_dirty_flags_.flLowCutOff)
+struct Mid1CenterValidator {
+    void operator()(float flMid1Center) const
     {
-        set_efx_low_cutoff();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid1 Center",
+            flMid1Center,
+            EAXEQUALIZER_MINMID1CENTER,
+            EAXEQUALIZER_MAXMID1CENTER);
     }
+}; // Mid1CenterValidator
 
-    if (eax_dirty_flags_.lMid1Gain)
+struct Mid1WidthValidator {
+    void operator()(float flMid1Width) const
     {
-        set_efx_mid1_gain();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid1 Width",
+            flMid1Width,
+            EAXEQUALIZER_MINMID1WIDTH,
+            EAXEQUALIZER_MAXMID1WIDTH);
     }
+}; // Mid1WidthValidator
 
-    if (eax_dirty_flags_.flMid1Center)
+struct Mid2GainValidator {
+    void operator()(long lMid2Gain) const
     {
-        set_efx_mid1_center();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid2 Gain",
+            lMid2Gain,
+            EAXEQUALIZER_MINMID2GAIN,
+            EAXEQUALIZER_MAXMID2GAIN);
     }
+}; // Mid2GainValidator
 
-    if (eax_dirty_flags_.flMid1Width)
+struct Mid2CenterValidator {
+    void operator()(float flMid2Center) const
     {
-        set_efx_mid1_width();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid2 Center",
+            flMid2Center,
+            EAXEQUALIZER_MINMID2CENTER,
+            EAXEQUALIZER_MAXMID2CENTER);
     }
+}; // Mid2CenterValidator
 
-    if (eax_dirty_flags_.lMid2Gain)
+struct Mid2WidthValidator {
+    void operator()(float flMid2Width) const
     {
-        set_efx_mid2_gain();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "Mid2 Width",
+            flMid2Width,
+            EAXEQUALIZER_MINMID2WIDTH,
+            EAXEQUALIZER_MAXMID2WIDTH);
     }
+}; // Mid2WidthValidator
 
-    if (eax_dirty_flags_.flMid2Center)
+struct HighGainValidator {
+    void operator()(long lHighGain) const
     {
-        set_efx_mid2_center();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "High Gain",
+            lHighGain,
+            EAXEQUALIZER_MINHIGHGAIN,
+            EAXEQUALIZER_MAXHIGHGAIN);
     }
+}; // HighGainValidator
 
-    if (eax_dirty_flags_.flMid2Width)
+struct HighCutOffValidator {
+    void operator()(float flHighCutOff) const
     {
-        set_efx_mid2_width();
+        eax_validate_range<EqualizerCommitter::Exception>(
+            "High Cutoff",
+            flHighCutOff,
+            EAXEQUALIZER_MINHIGHCUTOFF,
+            EAXEQUALIZER_MAXHIGHCUTOFF);
     }
+}; // HighCutOffValidator
 
-    if (eax_dirty_flags_.lHighGain)
+struct AllValidator {
+    void operator()(const EAXEQUALIZERPROPERTIES& all) const
     {
-        set_efx_high_gain();
+        LowGainValidator{}(all.lLowGain);
+        LowCutOffValidator{}(all.flLowCutOff);
+        Mid1GainValidator{}(all.lMid1Gain);
+        Mid1CenterValidator{}(all.flMid1Center);
+        Mid1WidthValidator{}(all.flMid1Width);
+        Mid2GainValidator{}(all.lMid2Gain);
+        Mid2CenterValidator{}(all.flMid2Center);
+        Mid2WidthValidator{}(all.flMid2Width);
+        HighGainValidator{}(all.lHighGain);
+        HighCutOffValidator{}(all.flHighCutOff);
     }
+}; // AllValidator
 
-    if (eax_dirty_flags_.flHighCutOff)
-    {
-        set_efx_high_cutoff();
-    }
+} // namespace
 
-    eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{};
+template<>
+struct EqualizerCommitter::Exception : public EaxException {
+    explicit Exception(const char* message) : EaxException{"EAX_EQUALIZER_EFFECT", message}
+    { }
+};
 
-    return true;
+template<>
+[[noreturn]] void EqualizerCommitter::fail(const char *message)
+{
+    throw Exception{message};
 }
 
-void EaxEqualizerEffect::set(const EaxEaxCall& eax_call)
+bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
-    {
-        case EAXEQUALIZER_NONE:
-            break;
-
-        case EAXEQUALIZER_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXEQUALIZER_LOWGAIN:
-            defer_low_gain(eax_call);
-            break;
-
-        case EAXEQUALIZER_LOWCUTOFF:
-            defer_low_cutoff(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID1GAIN:
-            defer_mid1_gain(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID1CENTER:
-            defer_mid1_center(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID1WIDTH:
-            defer_mid1_width(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID2GAIN:
-            defer_mid2_gain(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID2CENTER:
-            defer_mid2_center(eax_call);
-            break;
-
-        case EAXEQUALIZER_MID2WIDTH:
-            defer_mid2_width(eax_call);
-            break;
+    if(auto *cur = std::get_if<EAXEQUALIZERPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-        case EAXEQUALIZER_HIGHGAIN:
-            defer_high_gain(eax_call);
-            break;
+    mEaxProps = props;
+    mAlProps = [&]{
+        EqualizerProps ret{};
+        ret.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
+        ret.LowCutoff = props.flLowCutOff;
+        ret.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
+        ret.Mid1Center = props.flMid1Center;
+        ret.Mid1Width = props.flMid1Width;
+        ret.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
+        ret.Mid2Center = props.flMid2Center;
+        ret.Mid2Width = props.flMid2Width;
+        ret.HighGain = level_mb_to_gain(static_cast<float>(props.lHighGain));
+        ret.HighCutoff = props.flHighCutOff;
+        return ret;
+    }();
 
-        case EAXEQUALIZER_HIGHCUTOFF:
-            defer_high_cutoff(eax_call);
-            break;
+    return true;
+}
 
-        default:
-            throw EaxEqualizerEffectException{"Unsupported property id."};
+void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props)
+{
+    static constexpr EAXEQUALIZERPROPERTIES defprops{[]
+    {
+        EAXEQUALIZERPROPERTIES ret{};
+        ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
+        ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
+        ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
+        ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
+        ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
+        ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
+        ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
+        ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
+        ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
+        ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
+        return ret;
+    }()};
+    props = defprops;
+}
+
+void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props)
+{
+    switch(call.get_property_id())
+    {
+    case EAXEQUALIZER_NONE: break;
+    case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.lLowGain); break;
+    case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.flLowCutOff); break;
+    case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.lMid1Gain); break;
+    case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.flMid1Center); break;
+    case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.flMid1Width); break;
+    case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.lMid2Gain); break;
+    case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.flMid2Center); break;
+    case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.flMid2Width); break;
+    case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.lHighGain); break;
+    case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.flHighCutOff); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_equalizer_effect()
+void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props)
 {
-    return std::make_unique<EaxEqualizerEffect>();
+    switch(call.get_property_id())
+    {
+    case EAXEQUALIZER_NONE: break;
+    case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.lLowGain); break;
+    case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.flLowCutOff); break;
+    case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.lMid1Gain); break;
+    case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.flMid1Center); break;
+    case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.flMid1Width); break;
+    case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.lMid2Gain); break;
+    case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.flMid2Center); break;
+    case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.flMid2Width); break;
+    case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.lHighGain); break;
+    case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.flHighCutOff); break;
+    default: fail_unknown_property_id();
+    }
 }
 
 #endif // ALSOFT_EAX

+ 144 - 356
libs/openal-soft/al/effects/fshifter.cpp

@@ -1,38 +1,37 @@
 
 #include "config.h"
 
+#include <optional>
 #include <stdexcept>
 
 #include "AL/al.h"
 #include "AL/efx.h"
 
 #include "alc/effects/base.h"
-#include "aloptional.h"
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
 #include <cassert>
-
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-al::optional<FShifterDirection> DirectionFromEmum(ALenum value)
+constexpr std::optional<FShifterDirection> DirectionFromEmum(ALenum value) noexcept
 {
     switch(value)
     {
-    case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return al::make_optional(FShifterDirection::Down);
-    case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return al::make_optional(FShifterDirection::Up);
-    case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return al::make_optional(FShifterDirection::Off);
+    case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return FShifterDirection::Down;
+    case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up;
+    case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off;
     }
-    return al::nullopt;
+    return std::nullopt;
 }
-ALenum EnumFromDirection(FShifterDirection dir)
+constexpr ALenum EnumFromDirection(FShifterDirection dir)
 {
     switch(dir)
     {
@@ -43,31 +42,26 @@ ALenum EnumFromDirection(FShifterDirection dir)
     throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))};
 }
 
-void Fshifter_setParamf(EffectProps *props, ALenum param, float val)
+constexpr EffectProps genDefaultProps() noexcept
 {
-    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"};
-        props->Fshifter.Frequency = val;
-        break;
-
-    default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
-            param};
-    }
+    FshifterProps props{};
+    props.Frequency      = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
+    props.LeftDirection  = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value();
+    props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value();
+    return props;
 }
-void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Fshifter_setParamf(props, param, vals[0]); }
 
-void Fshifter_setParami(EffectProps *props, ALenum param, int val)
+} // namespace
+
+const EffectProps FshifterEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(FshifterProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
         if(auto diropt = DirectionFromEmum(val))
-            props->Fshifter.LeftDirection = *diropt;
+            props.LeftDirection = *diropt;
         else
             throw effect_exception{AL_INVALID_VALUE,
                 "Unsupported frequency shifter left direction: 0x%04x", val};
@@ -75,7 +69,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
 
     case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
         if(auto diropt = DirectionFromEmum(val))
-            props->Fshifter.RightDirection = *diropt;
+            props.RightDirection = *diropt;
         else
             throw effect_exception{AL_INVALID_VALUE,
                 "Unsupported frequency shifter right direction: 0x%04x", val};
@@ -86,33 +80,52 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
             "Invalid frequency shifter integer property 0x%04x", param};
     }
 }
-void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Fshifter_setParami(props, param, vals[0]); }
+void EffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, *vals); }
 
-void Fshifter_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::SetParamf(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"};
+        props.Frequency = val;
+        break;
+
+    default:
+        throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
+            param};
+    }
+}
+void EffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
+
+void EffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
-        *val = EnumFromDirection(props->Fshifter.LeftDirection);
+        *val = EnumFromDirection(props.LeftDirection);
         break;
     case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
-        *val = EnumFromDirection(props->Fshifter.RightDirection);
+        *val = EnumFromDirection(props.RightDirection);
         break;
+
     default:
         throw effect_exception{AL_INVALID_ENUM,
             "Invalid frequency shifter integer property 0x%04x", param};
     }
 }
-void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Fshifter_getParami(props, param, vals); }
+void EffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
 
-void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_FREQUENCY:
-        *val = props->Fshifter.Frequency;
+        *val = props.Frequency;
         break;
 
     default:
@@ -120,360 +133,135 @@ void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
             param};
     }
 }
-void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Fshifter_getParamf(props, param, vals); }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Fshifter.Frequency      = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
-    props.Fshifter.LeftDirection  = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION);
-    props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION);
-    return props;
-}
+void EffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Fshifter);
-
-const EffectProps FshifterEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxFrequencyShifterEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1;
-    EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1;
-    EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1;
-}; // EaxFrequencyShifterEffectDirtyFlags
-
-
-class EaxFrequencyShifterEffect final :
-    public EaxEffect
-{
-public:
-    EaxFrequencyShifterEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXFREQUENCYSHIFTERPROPERTIES eax_{};
-    EAXFREQUENCYSHIFTERPROPERTIES eax_d_{};
-    EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_frequency();
-    void set_efx_left_direction();
-    void set_efx_right_direction();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_frequency(float flFrequency);
-    void validate_left_direction(unsigned long ulLeftDirection);
-    void validate_right_direction(unsigned long ulRightDirection);
-    void validate_all(const EAXFREQUENCYSHIFTERPROPERTIES& all);
-
-    void defer_frequency(float flFrequency);
-    void defer_left_direction(unsigned long ulLeftDirection);
-    void defer_right_direction(unsigned long ulRightDirection);
-    void defer_all(const EAXFREQUENCYSHIFTERPROPERTIES& all);
-
-    void defer_frequency(const EaxEaxCall& eax_call);
-    void defer_left_direction(const EaxEaxCall& eax_call);
-    void defer_right_direction(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
+using FrequencyShifterCommitter = EaxCommitter<EaxFrequencyShifterCommitter>;
 
-    void set(const EaxEaxCall& eax_call);
-}; // EaxFrequencyShifterEffect
-
-
-class EaxFrequencyShifterEffectException :
-    public EaxException
-{
-public:
-    explicit EaxFrequencyShifterEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message}
+struct FrequencyValidator {
+    void operator()(float flFrequency) const
     {
+        eax_validate_range<FrequencyShifterCommitter::Exception>(
+            "Frequency",
+            flFrequency,
+            EAXFREQUENCYSHIFTER_MINFREQUENCY,
+            EAXFREQUENCYSHIFTER_MAXFREQUENCY);
     }
-}; // EaxFrequencyShifterEffectException
-
-
-EaxFrequencyShifterEffect::EaxFrequencyShifterEffect()
-    : EaxEffect{AL_EFFECT_FREQUENCY_SHIFTER}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxFrequencyShifterEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxFrequencyShifterEffect::set_eax_defaults()
-{
-    eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
-    eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
-    eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
-
-    eax_d_ = eax_;
-}
-
-void EaxFrequencyShifterEffect::set_efx_frequency()
-{
-    const auto frequency = clamp(
-        eax_.flFrequency,
-        AL_FREQUENCY_SHIFTER_MIN_FREQUENCY,
-        AL_FREQUENCY_SHIFTER_MAX_FREQUENCY);
-
-    al_effect_props_.Fshifter.Frequency = frequency;
-}
+}; // FrequencyValidator
 
-void EaxFrequencyShifterEffect::set_efx_left_direction()
-{
-    const auto left_direction = clamp(
-        static_cast<ALint>(eax_.ulLeftDirection),
-        AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION,
-        AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION);
-
-    const auto efx_left_direction = DirectionFromEmum(left_direction);
-    assert(efx_left_direction.has_value());
-    al_effect_props_.Fshifter.LeftDirection = *efx_left_direction;
-}
-
-void EaxFrequencyShifterEffect::set_efx_right_direction()
-{
-    const auto right_direction = clamp(
-        static_cast<ALint>(eax_.ulRightDirection),
-        AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION,
-        AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION);
-
-    const auto efx_right_direction = DirectionFromEmum(right_direction);
-    assert(efx_right_direction.has_value());
-    al_effect_props_.Fshifter.RightDirection = *efx_right_direction;
-}
-
-void EaxFrequencyShifterEffect::set_efx_defaults()
-{
-    set_efx_frequency();
-    set_efx_left_direction();
-    set_efx_right_direction();
-}
-
-void EaxFrequencyShifterEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct LeftDirectionValidator {
+    void operator()(unsigned long ulLeftDirection) const
     {
-        case EAXFREQUENCYSHIFTER_NONE:
-            break;
-
-        case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
-            eax_call.set_value<EaxFrequencyShifterEffectException>(eax_);
-            break;
-
-        case EAXFREQUENCYSHIFTER_FREQUENCY:
-            eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.flFrequency);
-            break;
-
-        case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
-            eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulLeftDirection);
-            break;
-
-        case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
-            eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulRightDirection);
-            break;
-
-        default:
-            throw EaxFrequencyShifterEffectException{"Unsupported property id."};
+        eax_validate_range<FrequencyShifterCommitter::Exception>(
+            "Left Direction",
+            ulLeftDirection,
+            EAXFREQUENCYSHIFTER_MINLEFTDIRECTION,
+            EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION);
     }
-}
-
-void EaxFrequencyShifterEffect::validate_frequency(
-    float flFrequency)
-{
-    eax_validate_range<EaxFrequencyShifterEffectException>(
-        "Frequency",
-        flFrequency,
-        EAXFREQUENCYSHIFTER_MINFREQUENCY,
-        EAXFREQUENCYSHIFTER_MAXFREQUENCY);
-}
+}; // LeftDirectionValidator
 
-void EaxFrequencyShifterEffect::validate_left_direction(
-    unsigned long ulLeftDirection)
-{
-    eax_validate_range<EaxFrequencyShifterEffectException>(
-        "Left Direction",
-        ulLeftDirection,
-        EAXFREQUENCYSHIFTER_MINLEFTDIRECTION,
-        EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION);
-}
-
-void EaxFrequencyShifterEffect::validate_right_direction(
-    unsigned long ulRightDirection)
-{
-    eax_validate_range<EaxFrequencyShifterEffectException>(
-        "Right Direction",
-        ulRightDirection,
-        EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION,
-        EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION);
-}
-
-void EaxFrequencyShifterEffect::validate_all(
-    const EAXFREQUENCYSHIFTERPROPERTIES& all)
-{
-    validate_frequency(all.flFrequency);
-    validate_left_direction(all.ulLeftDirection);
-    validate_right_direction(all.ulRightDirection);
-}
-
-void EaxFrequencyShifterEffect::defer_frequency(
-    float flFrequency)
-{
-    eax_d_.flFrequency = flFrequency;
-    eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
-}
+struct RightDirectionValidator {
+    void operator()(unsigned long ulRightDirection) const
+    {
+        eax_validate_range<FrequencyShifterCommitter::Exception>(
+            "Right Direction",
+            ulRightDirection,
+            EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION,
+            EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION);
+    }
+}; // RightDirectionValidator
 
-void EaxFrequencyShifterEffect::defer_left_direction(
-    unsigned long ulLeftDirection)
-{
-    eax_d_.ulLeftDirection = ulLeftDirection;
-    eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection);
-}
+struct AllValidator {
+    void operator()(const EAXFREQUENCYSHIFTERPROPERTIES& all) const
+    {
+        FrequencyValidator{}(all.flFrequency);
+        LeftDirectionValidator{}(all.ulLeftDirection);
+        RightDirectionValidator{}(all.ulRightDirection);
+    }
+}; // AllValidator
 
-void EaxFrequencyShifterEffect::defer_right_direction(
-    unsigned long ulRightDirection)
-{
-    eax_d_.ulRightDirection = ulRightDirection;
-    eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection);
-}
+} // namespace
 
-void EaxFrequencyShifterEffect::defer_all(
-    const EAXFREQUENCYSHIFTERPROPERTIES& all)
-{
-    defer_frequency(all.flFrequency);
-    defer_left_direction(all.ulLeftDirection);
-    defer_right_direction(all.ulRightDirection);
-}
+template<>
+struct FrequencyShifterCommitter::Exception : public EaxException {
+    explicit Exception(const char *message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message}
+    { }
+};
 
-void EaxFrequencyShifterEffect::defer_frequency(
-    const EaxEaxCall& eax_call)
+template<>
+[[noreturn]] void FrequencyShifterCommitter::fail(const char *message)
 {
-    const auto& frequency =
-        eax_call.get_value<
-        EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>();
-
-    validate_frequency(frequency);
-    defer_frequency(frequency);
+    throw Exception{message};
 }
 
-void EaxFrequencyShifterEffect::defer_left_direction(
-    const EaxEaxCall& eax_call)
+bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props)
 {
-    const auto& left_direction =
-        eax_call.get_value<
-        EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>();
+    if(auto *cur = std::get_if<EAXFREQUENCYSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-    validate_left_direction(left_direction);
-    defer_left_direction(left_direction);
-}
+    mEaxProps = props;
 
-void EaxFrequencyShifterEffect::defer_right_direction(
-    const EaxEaxCall& eax_call)
-{
-    const auto& right_direction =
-        eax_call.get_value<
-        EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>();
+    auto get_direction = [](unsigned long dir) noexcept
+    {
+        if(dir == EAX_FREQUENCYSHIFTER_DOWN)
+            return FShifterDirection::Down;
+        if(dir == EAX_FREQUENCYSHIFTER_UP)
+            return FShifterDirection::Up;
+        return FShifterDirection::Off;
+    };
+
+    mAlProps = [&]{
+        FshifterProps ret{};
+        ret.Frequency = props.flFrequency;
+        ret.LeftDirection = get_direction(props.ulLeftDirection);
+        ret.RightDirection = get_direction(props.ulRightDirection);
+        return ret;
+    }();
 
-    validate_right_direction(right_direction);
-    defer_right_direction(right_direction);
+    return true;
 }
 
-void EaxFrequencyShifterEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& all =
-        eax_call.get_value<
-        EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[]
+    {
+        EAXFREQUENCYSHIFTERPROPERTIES ret{};
+        ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
+        ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
+        ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
+        return ret;
+    }()};
+    props = defprops;
 }
 
-// [[nodiscard]]
-bool EaxFrequencyShifterEffect::apply_deferred()
+void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props)
 {
-    if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.flFrequency)
-    {
-        set_efx_frequency();
-    }
-
-    if (eax_dirty_flags_.ulLeftDirection)
-    {
-        set_efx_left_direction();
-    }
-
-    if (eax_dirty_flags_.ulRightDirection)
+    switch(call.get_property_id())
     {
-        set_efx_right_direction();
+    case EAXFREQUENCYSHIFTER_NONE: break;
+    case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
+    case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.ulLeftDirection); break;
+    case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.ulRightDirection); break;
+    default: fail_unknown_property_id();
     }
-
-    eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxFrequencyShifterEffect::set(const EaxEaxCall& eax_call)
+void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXFREQUENCYSHIFTER_NONE:
-            break;
-
-        case EAXFREQUENCYSHIFTER_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXFREQUENCYSHIFTER_FREQUENCY:
-            defer_frequency(eax_call);
-            break;
-
-        case EAXFREQUENCYSHIFTER_LEFTDIRECTION:
-            defer_left_direction(eax_call);
-            break;
-
-        case EAXFREQUENCYSHIFTER_RIGHTDIRECTION:
-            defer_right_direction(eax_call);
-            break;
-
-        default:
-            throw EaxFrequencyShifterEffectException{"Unsupported property id."};
+    case EAXFREQUENCYSHIFTER_NONE: break;
+    case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
+    case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.ulLeftDirection); break;
+    case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.ulRightDirection); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_frequency_shifter_effect()
-{
-    return std::make_unique<EaxFrequencyShifterEffect>();
-}
-
 #endif // ALSOFT_EAX

+ 148 - 365
libs/openal-soft/al/effects/modulator.cpp

@@ -1,38 +1,37 @@
 
 #include "config.h"
 
+#include <optional>
 #include <stdexcept>
 
 #include "AL/al.h"
 #include "AL/efx.h"
 
 #include "alc/effects/base.h"
-#include "aloptional.h"
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
 #include <cassert>
-
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value)
+constexpr std::optional<ModulatorWaveform> WaveformFromEmum(ALenum value) noexcept
 {
     switch(value)
     {
-    case AL_RING_MODULATOR_SINUSOID: return al::make_optional(ModulatorWaveform::Sinusoid);
-    case AL_RING_MODULATOR_SAWTOOTH: return al::make_optional(ModulatorWaveform::Sawtooth);
-    case AL_RING_MODULATOR_SQUARE: return al::make_optional(ModulatorWaveform::Square);
+    case AL_RING_MODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid;
+    case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth;
+    case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square;
     }
-    return al::nullopt;
+    return std::nullopt;
 }
-ALenum EnumFromWaveform(ModulatorWaveform type)
+constexpr ALenum EnumFromWaveform(ModulatorWaveform type)
 {
     switch(type)
     {
@@ -44,40 +43,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type)
         std::to_string(static_cast<int>(type))};
 }
 
-void Modulator_setParamf(EffectProps *props, ALenum param, float val)
+constexpr EffectProps genDefaultProps() noexcept
 {
-    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};
-        props->Modulator.Frequency = val;
-        break;
+    ModulatorProps props{};
+    props.Frequency      = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
+    props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
+    props.Waveform       = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value();
+    return props;
+}
 
-    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};
-        props->Modulator.HighPassCutoff = val;
-        break;
+} // namespace
 
-    default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
-    }
-}
-void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Modulator_setParamf(props, param, vals[0]); }
-void Modulator_setParami(EffectProps *props, ALenum param, int val)
+const EffectProps ModulatorEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(ModulatorProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_RING_MODULATOR_FREQUENCY:
     case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-        Modulator_setParamf(props, param, static_cast<float>(val));
+        SetParamf(props, param, static_cast<float>(val));
         break;
 
     case AL_RING_MODULATOR_WAVEFORM:
         if(auto formopt = WaveformFromEmum(val))
-            props->Modulator.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val};
         break;
@@ -87,396 +77,189 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Modulator_setParami(props, param, vals[0]); }
+void EffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, *vals); }
 
-void Modulator_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val)
 {
     switch(param)
     {
     case AL_RING_MODULATOR_FREQUENCY:
-        *val = static_cast<int>(props->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};
+        props.Frequency = val;
         break;
+
     case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-        *val = static_cast<int>(props->Modulator.HighPassCutoff);
-        break;
-    case AL_RING_MODULATOR_WAVEFORM:
-        *val = EnumFromWaveform(props->Modulator.Waveform);
+        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};
+        props.HighPassCutoff = val;
         break;
 
+    default:
+        throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
+    }
+}
+void EffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
+
+void EffectHandler::GetParami(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};
     }
 }
-void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Modulator_getParami(props, param, vals); }
-void Modulator_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_RING_MODULATOR_FREQUENCY:
-        *val = props->Modulator.Frequency;
-        break;
-    case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-        *val = props->Modulator.HighPassCutoff;
-        break;
+    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};
     }
 }
-void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Modulator_getParamf(props, param, vals); }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Modulator.Frequency      = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
-    props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
-    props.Modulator.Waveform       = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM);
-    return props;
-}
-
-} // namespace
+void EffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-DEFINE_ALEFFECT_VTABLE(Modulator);
-
-const EffectProps ModulatorEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxRingModulatorEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1;
-    EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1;
-    EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1;
-}; // EaxPitchShifterEffectDirtyFlags
-
-
-class EaxRingModulatorEffect final :
-    public EaxEffect
-{
-public:
-    EaxRingModulatorEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXRINGMODULATORPROPERTIES eax_{};
-    EAXRINGMODULATORPROPERTIES eax_d_{};
-    EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
+using ModulatorCommitter = EaxCommitter<EaxModulatorCommitter>;
 
-    void set_efx_frequency();
-    void set_efx_high_pass_cutoff();
-    void set_efx_waveform();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_frequency(float flFrequency);
-    void validate_high_pass_cutoff(float flHighPassCutOff);
-    void validate_waveform(unsigned long ulWaveform);
-    void validate_all(const EAXRINGMODULATORPROPERTIES& all);
-
-    void defer_frequency(float flFrequency);
-    void defer_high_pass_cutoff(float flHighPassCutOff);
-    void defer_waveform(unsigned long ulWaveform);
-    void defer_all(const EAXRINGMODULATORPROPERTIES& all);
-
-    void defer_frequency(const EaxEaxCall& eax_call);
-    void defer_high_pass_cutoff(const EaxEaxCall& eax_call);
-    void defer_waveform(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxRingModulatorEffect
-
-
-class EaxRingModulatorEffectException :
-    public EaxException
-{
-public:
-    explicit EaxRingModulatorEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_RING_MODULATOR_EFFECT", message}
+struct FrequencyValidator {
+    void operator()(float flFrequency) const
     {
+        eax_validate_range<ModulatorCommitter::Exception>(
+            "Frequency",
+            flFrequency,
+            EAXRINGMODULATOR_MINFREQUENCY,
+            EAXRINGMODULATOR_MAXFREQUENCY);
     }
-}; // EaxRingModulatorEffectException
-
-
-EaxRingModulatorEffect::EaxRingModulatorEffect()
-    : EaxEffect{AL_EFFECT_RING_MODULATOR}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxRingModulatorEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxRingModulatorEffect::set_eax_defaults()
-{
-    eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
-    eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
-    eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
-
-    eax_d_ = eax_;
-}
+}; // FrequencyValidator
 
-void EaxRingModulatorEffect::set_efx_frequency()
-{
-    const auto frequency = clamp(
-        eax_.flFrequency,
-        AL_RING_MODULATOR_MIN_FREQUENCY,
-        AL_RING_MODULATOR_MAX_FREQUENCY);
-
-    al_effect_props_.Modulator.Frequency = frequency;
-}
-
-void EaxRingModulatorEffect::set_efx_high_pass_cutoff()
-{
-    const auto high_pass_cutoff = clamp(
-        eax_.flHighPassCutOff,
-        AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF,
-        AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF);
-
-    al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff;
-}
-
-void EaxRingModulatorEffect::set_efx_waveform()
-{
-    const auto waveform = clamp(
-        static_cast<ALint>(eax_.ulWaveform),
-        AL_RING_MODULATOR_MIN_WAVEFORM,
-        AL_RING_MODULATOR_MAX_WAVEFORM);
-
-    const auto efx_waveform = WaveformFromEmum(waveform);
-    assert(efx_waveform.has_value());
-    al_effect_props_.Modulator.Waveform = *efx_waveform;
-}
-
-void EaxRingModulatorEffect::set_efx_defaults()
-{
-    set_efx_frequency();
-    set_efx_high_pass_cutoff();
-    set_efx_waveform();
-}
-
-void EaxRingModulatorEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct HighPassCutOffValidator {
+    void operator()(float flHighPassCutOff) const
     {
-        case EAXRINGMODULATOR_NONE:
-            break;
-
-        case EAXRINGMODULATOR_ALLPARAMETERS:
-            eax_call.set_value<EaxRingModulatorEffectException>(eax_);
-            break;
-
-        case EAXRINGMODULATOR_FREQUENCY:
-            eax_call.set_value<EaxRingModulatorEffectException>(eax_.flFrequency);
-            break;
-
-        case EAXRINGMODULATOR_HIGHPASSCUTOFF:
-            eax_call.set_value<EaxRingModulatorEffectException>(eax_.flHighPassCutOff);
-            break;
-
-        case EAXRINGMODULATOR_WAVEFORM:
-            eax_call.set_value<EaxRingModulatorEffectException>(eax_.ulWaveform);
-            break;
-
-        default:
-            throw EaxRingModulatorEffectException{"Unsupported property id."};
+        eax_validate_range<ModulatorCommitter::Exception>(
+            "High-Pass Cutoff",
+            flHighPassCutOff,
+            EAXRINGMODULATOR_MINHIGHPASSCUTOFF,
+            EAXRINGMODULATOR_MAXHIGHPASSCUTOFF);
     }
-}
-
-void EaxRingModulatorEffect::validate_frequency(
-    float flFrequency)
-{
-    eax_validate_range<EaxRingModulatorEffectException>(
-        "Frequency",
-        flFrequency,
-        EAXRINGMODULATOR_MINFREQUENCY,
-        EAXRINGMODULATOR_MAXFREQUENCY);
-}
-
-void EaxRingModulatorEffect::validate_high_pass_cutoff(
-    float flHighPassCutOff)
-{
-    eax_validate_range<EaxRingModulatorEffectException>(
-        "High-Pass Cutoff",
-        flHighPassCutOff,
-        EAXRINGMODULATOR_MINHIGHPASSCUTOFF,
-        EAXRINGMODULATOR_MAXHIGHPASSCUTOFF);
-}
-
-void EaxRingModulatorEffect::validate_waveform(
-    unsigned long ulWaveform)
-{
-    eax_validate_range<EaxRingModulatorEffectException>(
-        "Waveform",
-        ulWaveform,
-        EAXRINGMODULATOR_MINWAVEFORM,
-        EAXRINGMODULATOR_MAXWAVEFORM);
-}
+}; // HighPassCutOffValidator
 
-void EaxRingModulatorEffect::validate_all(
-    const EAXRINGMODULATORPROPERTIES& all)
-{
-    validate_frequency(all.flFrequency);
-    validate_high_pass_cutoff(all.flHighPassCutOff);
-    validate_waveform(all.ulWaveform);
-}
-
-void EaxRingModulatorEffect::defer_frequency(
-    float flFrequency)
-{
-    eax_d_.flFrequency = flFrequency;
-    eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency);
-}
+struct WaveformValidator {
+    void operator()(unsigned long ulWaveform) const
+    {
+        eax_validate_range<ModulatorCommitter::Exception>(
+            "Waveform",
+            ulWaveform,
+            EAXRINGMODULATOR_MINWAVEFORM,
+            EAXRINGMODULATOR_MAXWAVEFORM);
+    }
+}; // WaveformValidator
 
-void EaxRingModulatorEffect::defer_high_pass_cutoff(
-    float flHighPassCutOff)
-{
-    eax_d_.flHighPassCutOff = flHighPassCutOff;
-    eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff);
-}
+struct AllValidator {
+    void operator()(const EAXRINGMODULATORPROPERTIES& all) const
+    {
+        FrequencyValidator{}(all.flFrequency);
+        HighPassCutOffValidator{}(all.flHighPassCutOff);
+        WaveformValidator{}(all.ulWaveform);
+    }
+}; // AllValidator
 
-void EaxRingModulatorEffect::defer_waveform(
-    unsigned long ulWaveform)
-{
-    eax_d_.ulWaveform = ulWaveform;
-    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
-}
+} // namespace
 
-void EaxRingModulatorEffect::defer_all(
-    const EAXRINGMODULATORPROPERTIES& all)
-{
-    defer_frequency(all.flFrequency);
-    defer_high_pass_cutoff(all.flHighPassCutOff);
-    defer_waveform(all.ulWaveform);
-}
+template<>
+struct ModulatorCommitter::Exception : public EaxException {
+    explicit Exception(const char *message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message}
+    { }
+};
 
-void EaxRingModulatorEffect::defer_frequency(
-    const EaxEaxCall& eax_call)
+template<>
+[[noreturn]] void ModulatorCommitter::fail(const char *message)
 {
-    const auto& frequency =
-        eax_call.get_value<
-        EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>();
-
-    validate_frequency(frequency);
-    defer_frequency(frequency);
+    throw Exception{message};
 }
 
-void EaxRingModulatorEffect::defer_high_pass_cutoff(
-    const EaxEaxCall& eax_call)
+bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props)
 {
-    const auto& high_pass_cutoff =
-        eax_call.get_value<
-        EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>();
+    if(auto *cur = std::get_if<EAXRINGMODULATORPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-    validate_high_pass_cutoff(high_pass_cutoff);
-    defer_high_pass_cutoff(high_pass_cutoff);
-}
+    mEaxProps = props;
 
-void EaxRingModulatorEffect::defer_waveform(
-    const EaxEaxCall& eax_call)
-{
-    const auto& waveform =
-        eax_call.get_value<
-        EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>();
+    auto get_waveform = [](unsigned long form)
+    {
+        if(form == EAX_RINGMODULATOR_SINUSOID)
+            return ModulatorWaveform::Sinusoid;
+        if(form == EAX_RINGMODULATOR_SAWTOOTH)
+            return ModulatorWaveform::Sawtooth;
+        if(form == EAX_RINGMODULATOR_SQUARE)
+            return ModulatorWaveform::Square;
+        return ModulatorWaveform::Sinusoid;
+    };
+
+    mAlProps = [&]{
+        ModulatorProps ret{};
+        ret.Frequency = props.flFrequency;
+        ret.HighPassCutoff = props.flHighPassCutOff;
+        ret.Waveform = get_waveform(props.ulWaveform);
+        return ret;
+    }();
 
-    validate_waveform(waveform);
-    defer_waveform(waveform);
+    return true;
 }
 
-void EaxRingModulatorEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& all =
-        eax_call.get_value<EaxRingModulatorEffectException, const EAXRINGMODULATORPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    static constexpr EAXRINGMODULATORPROPERTIES defprops{[]
+    {
+        EAXRINGMODULATORPROPERTIES ret{};
+        ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
+        ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
+        ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
+        return ret;
+    }()};
+    props = defprops;
 }
 
-// [[nodiscard]]
-bool EaxRingModulatorEffect::apply_deferred()
+void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props)
 {
-    if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.flFrequency)
+    switch(call.get_property_id())
     {
-        set_efx_frequency();
+    case EAXRINGMODULATOR_NONE: break;
+    case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
+    case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.flHighPassCutOff); break;
+    case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
+    default: fail_unknown_property_id();
     }
-
-    if (eax_dirty_flags_.flHighPassCutOff)
-    {
-        set_efx_high_pass_cutoff();
-    }
-
-    if (eax_dirty_flags_.ulWaveform)
-    {
-        set_efx_waveform();
-    }
-
-    eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxRingModulatorEffect::set(const EaxEaxCall& eax_call)
+void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props)
 {
-    switch (eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXRINGMODULATOR_NONE:
-            break;
-
-        case EAXRINGMODULATOR_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXRINGMODULATOR_FREQUENCY:
-            defer_frequency(eax_call);
-            break;
-
-        case EAXRINGMODULATOR_HIGHPASSCUTOFF:
-            defer_high_pass_cutoff(eax_call);
-            break;
-
-        case EAXRINGMODULATOR_WAVEFORM:
-            defer_waveform(eax_call);
-            break;
-
-        default:
-            throw EaxRingModulatorEffectException{"Unsupported property id."};
+    case EAXRINGMODULATOR_NONE: break;
+    case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
+    case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.flHighPassCutOff); break;
+    case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_ring_modulator_effect()
-{
-    return std::make_unique<EaxRingModulatorEffect>();
-}
-
 #endif // ALSOFT_EAX

+ 48 - 57
libs/openal-soft/al/effects/null.cpp

@@ -8,13 +8,23 @@
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
-#include "al/eax_exception.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    return std::monostate{};
+}
+
+} // namespace
+
+const EffectProps NullEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/)
 {
     switch(param)
     {
@@ -23,15 +33,15 @@ void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
             param};
     }
 }
-void Null_setParamiv(EffectProps *props, ALenum param, const int *vals)
+void EffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals)
 {
     switch(param)
     {
     default:
-        Null_setParami(props, param, vals[0]);
+        SetParami(props, param, *vals);
     }
 }
-void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
+void EffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/)
 {
     switch(param)
     {
@@ -40,16 +50,16 @@ void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
             param};
     }
 }
-void Null_setParamfv(EffectProps *props, ALenum param, const float *vals)
+void EffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals)
 {
     switch(param)
     {
     default:
-        Null_setParamf(props, param, vals[0]);
+        SetParamf(props, param, *vals);
     }
 }
 
-void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
+void EffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/)
 {
     switch(param)
     {
@@ -58,15 +68,15 @@ void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
             param};
     }
 }
-void Null_getParamiv(const EffectProps *props, ALenum param, int *vals)
+void EffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals)
 {
     switch(param)
     {
     default:
-        Null_getParami(props, param, vals);
+        GetParami(props, param, vals);
     }
 }
-void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
+void EffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/)
 {
     switch(param)
     {
@@ -75,78 +85,59 @@ void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
             param};
     }
 }
-void Null_getParamfv(const EffectProps *props, ALenum param, float *vals)
+void EffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals)
 {
     switch(param)
     {
     default:
-        Null_getParamf(props, param, vals);
+        GetParamf(props, param, vals);
     }
 }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Null);
-
-const EffectProps NullEffectProps{genDefaultProps()};
-
 
 #ifdef ALSOFT_EAX
 namespace {
 
-class EaxNullEffect final :
-    public EaxEffect
-{
-public:
-    EaxNullEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-}; // EaxNullEffect
+using NullCommitter = EaxCommitter<EaxNullCommitter>;
 
+} // namespace
 
-class EaxNullEffectException :
-    public EaxException
+template<>
+struct NullCommitter::Exception : public EaxException
 {
-public:
-    explicit EaxNullEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_NULL_EFFECT", message}
-    {
-    }
-}; // EaxNullEffectException
-
+    explicit Exception(const char *message) : EaxException{"EAX_NULL_EFFECT", message}
+    { }
+};
 
-EaxNullEffect::EaxNullEffect()
-    : EaxEffect{AL_EFFECT_NULL}
+template<>
+[[noreturn]] void NullCommitter::fail(const char *message)
 {
+    throw Exception{message};
 }
 
-void EaxNullEffect::dispatch(const EaxEaxCall& eax_call)
+bool EaxNullCommitter::commit(const std::monostate &props)
 {
-    if(eax_call.get_property_id() != 0)
-        throw EaxNullEffectException{"Unsupported property id."};
+    const bool ret{std::holds_alternative<std::monostate>(mEaxProps)};
+    mEaxProps = props;
+    mAlProps = std::monostate{};
+    return ret;
 }
 
-bool EaxNullEffect::apply_deferred()
+void EaxNullCommitter::SetDefaults(EaxEffectProps &props)
 {
-    return false;
+    props = std::monostate{};
 }
 
-} // namespace
+void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&)
+{
+    if(call.get_property_id() != 0)
+        fail_unknown_property_id();
+}
 
-EaxEffectUPtr eax_create_eax_null_effect()
+void EaxNullCommitter::Set(const EaxCall &call, std::monostate&)
 {
-    return std::make_unique<EaxNullEffect>();
+    if(call.get_property_id() != 0)
+        fail_unknown_property_id();
 }
 
 #endif // ALSOFT_EAX

+ 93 - 274
libs/openal-soft/al/effects/pshifter.cpp

@@ -9,36 +9,40 @@
 
 #ifdef ALSOFT_EAX
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Pshifter_setParamf(EffectProps*, ALenum param, float)
-{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
-void Pshifter_setParamfv(EffectProps*, ALenum param, const float*)
+constexpr EffectProps genDefaultProps() noexcept
 {
-    throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
-        param};
+    PshifterProps props{};
+    props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
+    props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
+    return props;
 }
 
-void Pshifter_setParami(EffectProps *props, ALenum param, int val)
+} // namespace
+
+const EffectProps PshifterEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(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"};
-        props->Pshifter.CoarseTune = val;
+        props.CoarseTune = val;
         break;
 
     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"};
-        props->Pshifter.FineTune = val;
+        props.FineTune = val;
         break;
 
     default:
@@ -46,319 +50,134 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Pshifter_setParami(props, param, vals[0]); }
+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};
+}
 
-void Pshifter_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_PITCH_SHIFTER_COARSE_TUNE:
-        *val = props->Pshifter.CoarseTune;
-        break;
-    case AL_PITCH_SHIFTER_FINE_TUNE:
-        *val = props->Pshifter.FineTune;
-        break;
+    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};
     }
 }
-void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Pshifter_getParami(props, param, vals); }
+void EffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
 
-void Pshifter_getParamf(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamf(const PshifterProps&, ALenum param, float*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
-void Pshifter_getParamfv(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x",
         param};
 }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
-    props.Pshifter.FineTune   = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Pshifter);
-
-const EffectProps PshifterEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxPitchShifterEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
+using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>;
 
-    EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1;
-    EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1;
-}; // EaxPitchShifterEffectDirtyFlags
-
-
-class EaxPitchShifterEffect final :
-    public EaxEffect
-{
-public:
-    EaxPitchShifterEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXPITCHSHIFTERPROPERTIES eax_{};
-    EAXPITCHSHIFTERPROPERTIES eax_d_{};
-    EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_coarse_tune();
-    void set_efx_fine_tune();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_coarse_tune(long lCoarseTune);
-    void validate_fine_tune(long lFineTune);
-    void validate_all(const EAXPITCHSHIFTERPROPERTIES& all);
-
-    void defer_coarse_tune(long lCoarseTune);
-    void defer_fine_tune(long lFineTune);
-    void defer_all(const EAXPITCHSHIFTERPROPERTIES& all);
-
-    void defer_coarse_tune(const EaxEaxCall& eax_call);
-    void defer_fine_tune(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxPitchShifterEffect
-
-
-class EaxPitchShifterEffectException :
-    public EaxException
-{
-public:
-    explicit EaxPitchShifterEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_PITCH_SHIFTER_EFFECT", message}
+struct CoarseTuneValidator {
+    void operator()(long lCoarseTune) const
     {
+        eax_validate_range<PitchShifterCommitter::Exception>(
+            "Coarse Tune",
+            lCoarseTune,
+            EAXPITCHSHIFTER_MINCOARSETUNE,
+            EAXPITCHSHIFTER_MAXCOARSETUNE);
     }
-}; // EaxPitchShifterEffectException
-
-
-EaxPitchShifterEffect::EaxPitchShifterEffect()
-    : EaxEffect{AL_EFFECT_PITCH_SHIFTER}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxPitchShifterEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxPitchShifterEffect::set_eax_defaults()
-{
-    eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE;
-    eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
-
-    eax_d_ = eax_;
-}
-
-void EaxPitchShifterEffect::set_efx_coarse_tune()
-{
-    const auto coarse_tune = clamp(
-        static_cast<ALint>(eax_.lCoarseTune),
-        AL_PITCH_SHIFTER_MIN_COARSE_TUNE,
-        AL_PITCH_SHIFTER_MAX_COARSE_TUNE);
-
-    al_effect_props_.Pshifter.CoarseTune = coarse_tune;
-}
-
-void EaxPitchShifterEffect::set_efx_fine_tune()
-{
-    const auto fine_tune = clamp(
-        static_cast<ALint>(eax_.lFineTune),
-        AL_PITCH_SHIFTER_MIN_FINE_TUNE,
-        AL_PITCH_SHIFTER_MAX_FINE_TUNE);
-
-    al_effect_props_.Pshifter.FineTune = fine_tune;
-}
+}; // CoarseTuneValidator
 
-void EaxPitchShifterEffect::set_efx_defaults()
-{
-    set_efx_coarse_tune();
-    set_efx_fine_tune();
-}
-
-void EaxPitchShifterEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct FineTuneValidator {
+    void operator()(long lFineTune) const
     {
-        case EAXPITCHSHIFTER_NONE:
-            break;
-
-        case EAXPITCHSHIFTER_ALLPARAMETERS:
-            eax_call.set_value<EaxPitchShifterEffectException>(eax_);
-            break;
-
-        case EAXPITCHSHIFTER_COARSETUNE:
-            eax_call.set_value<EaxPitchShifterEffectException>(eax_.lCoarseTune);
-            break;
-
-        case EAXPITCHSHIFTER_FINETUNE:
-            eax_call.set_value<EaxPitchShifterEffectException>(eax_.lFineTune);
-            break;
-
-        default:
-            throw EaxPitchShifterEffectException{"Unsupported property id."};
+        eax_validate_range<PitchShifterCommitter::Exception>(
+            "Fine Tune",
+            lFineTune,
+            EAXPITCHSHIFTER_MINFINETUNE,
+            EAXPITCHSHIFTER_MAXFINETUNE);
     }
-}
-
-void EaxPitchShifterEffect::validate_coarse_tune(
-    long lCoarseTune)
-{
-    eax_validate_range<EaxPitchShifterEffectException>(
-        "Coarse Tune",
-        lCoarseTune,
-        EAXPITCHSHIFTER_MINCOARSETUNE,
-        EAXPITCHSHIFTER_MAXCOARSETUNE);
-}
+}; // FineTuneValidator
 
-void EaxPitchShifterEffect::validate_fine_tune(
-    long lFineTune)
-{
-    eax_validate_range<EaxPitchShifterEffectException>(
-        "Fine Tune",
-        lFineTune,
-        EAXPITCHSHIFTER_MINFINETUNE,
-        EAXPITCHSHIFTER_MAXFINETUNE);
-}
+struct AllValidator {
+    void operator()(const EAXPITCHSHIFTERPROPERTIES& all) const
+    {
+        CoarseTuneValidator{}(all.lCoarseTune);
+        FineTuneValidator{}(all.lFineTune);
+    }
+}; // AllValidator
 
-void EaxPitchShifterEffect::validate_all(
-    const EAXPITCHSHIFTERPROPERTIES& all)
-{
-    validate_coarse_tune(all.lCoarseTune);
-    validate_fine_tune(all.lFineTune);
-}
+} // namespace
 
-void EaxPitchShifterEffect::defer_coarse_tune(
-    long lCoarseTune)
-{
-    eax_d_.lCoarseTune = lCoarseTune;
-    eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune);
-}
+template<>
+struct PitchShifterCommitter::Exception : public EaxException {
+    explicit Exception(const char *message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message}
+    { }
+};
 
-void EaxPitchShifterEffect::defer_fine_tune(
-    long lFineTune)
+template<>
+[[noreturn]] void PitchShifterCommitter::fail(const char *message)
 {
-    eax_d_.lFineTune = lFineTune;
-    eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune);
+    throw Exception{message};
 }
 
-void EaxPitchShifterEffect::defer_all(
-    const EAXPITCHSHIFTERPROPERTIES& all)
+bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props)
 {
-    defer_coarse_tune(all.lCoarseTune);
-    defer_fine_tune(all.lFineTune);
-}
-
-void EaxPitchShifterEffect::defer_coarse_tune(
-    const EaxEaxCall& eax_call)
-{
-    const auto& coarse_tune =
-        eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lCoarseTune)>();
-
-    validate_coarse_tune(coarse_tune);
-    defer_coarse_tune(coarse_tune);
-}
+    if(auto *cur = std::get_if<EAXPITCHSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-void EaxPitchShifterEffect::defer_fine_tune(
-    const EaxEaxCall& eax_call)
-{
-    const auto& fine_tune =
-        eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lFineTune)>();
+    mEaxProps = props;
+    mAlProps = [&]{
+        PshifterProps ret{};
+        ret.CoarseTune = static_cast<int>(props.lCoarseTune);
+        ret.FineTune = static_cast<int>(props.lFineTune);
+        return ret;
+    }();
 
-    validate_fine_tune(fine_tune);
-    defer_fine_tune(fine_tune);
+    return true;
 }
 
-void EaxPitchShifterEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& all =
-        eax_call.get_value<EaxPitchShifterEffectException, const EAXPITCHSHIFTERPROPERTIES>();
-
-    validate_all(all);
-    defer_all(all);
+    props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE,
+        EAXPITCHSHIFTER_DEFAULTFINETUNE};
 }
 
-// [[nodiscard]]
-bool EaxPitchShifterEffect::apply_deferred()
+void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props)
 {
-    if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{})
-    {
-        return false;
-    }
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.lCoarseTune)
-    {
-        set_efx_coarse_tune();
-    }
-
-    if (eax_dirty_flags_.lFineTune)
+    switch(call.get_property_id())
     {
-        set_efx_fine_tune();
+    case EAXPITCHSHIFTER_NONE: break;
+    case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.lCoarseTune); break;
+    case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.lFineTune); break;
+    default: fail_unknown_property_id();
     }
-
-    eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxPitchShifterEffect::set(const EaxEaxCall& eax_call)
+void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXPITCHSHIFTER_NONE:
-            break;
-
-        case EAXPITCHSHIFTER_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXPITCHSHIFTER_COARSETUNE:
-            defer_coarse_tune(eax_call);
-            break;
-
-        case EAXPITCHSHIFTER_FINETUNE:
-            defer_fine_tune(eax_call);
-            break;
-
-        default:
-            throw EaxPitchShifterEffectException{"Unsupported property id."};
+    case EAXPITCHSHIFTER_NONE: break;
+    case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.lCoarseTune); break;
+    case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.lFineTune); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-EaxEffectUPtr eax_create_eax_pitch_shifter_effect()
-{
-    return std::make_unique<EaxPitchShifterEffect>();
-}
-
 #endif // ALSOFT_EAX

+ 867 - 2157
libs/openal-soft/al/effects/reverb.cpp

@@ -1,33 +1,105 @@
 
 #include "config.h"
 
+#include <algorithm>
+#include <array>
 #include <cmath>
+#include <variant>
 
 #include "AL/al.h"
 #include "AL/efx.h"
 
-#include "alc/effects/base.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/effects/base.h"
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
-#include <tuple>
-#include "alnumeric.h"
-#include "AL/efx-presets.h"
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include <cassert>
+#include "al/eax/api.h"
+#include "al/eax/call.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-void Reverb_setParami(EffectProps *props, ALenum param, int val)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    ReverbProps props{};
+    props.Density   = AL_EAXREVERB_DEFAULT_DENSITY;
+    props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
+    props.Gain   = AL_EAXREVERB_DEFAULT_GAIN;
+    props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
+    props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
+    props.DecayTime    = AL_EAXREVERB_DEFAULT_DECAY_TIME;
+    props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
+    props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
+    props.ReflectionsGain   = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
+    props.ReflectionsDelay  = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
+    props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.LateReverbGain   = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
+    props.LateReverbDelay  = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
+    props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.EchoTime  = AL_EAXREVERB_DEFAULT_ECHO_TIME;
+    props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
+    props.ModulationTime  = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
+    props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
+    props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+    props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
+    props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
+    props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+    props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
+    return props;
+}
+
+constexpr EffectProps genDefaultStdProps() noexcept
+{
+    ReverbProps props{};
+    props.Density   = AL_REVERB_DEFAULT_DENSITY;
+    props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
+    props.Gain   = AL_REVERB_DEFAULT_GAIN;
+    props.GainHF = AL_REVERB_DEFAULT_GAINHF;
+    props.GainLF = 1.0f;
+    props.DecayTime    = AL_REVERB_DEFAULT_DECAY_TIME;
+    props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
+    props.DecayLFRatio = 1.0f;
+    props.ReflectionsGain  = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
+    props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
+    props.ReflectionsPan   = {0.0f, 0.0f, 0.0f};
+    props.LateReverbGain  = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
+    props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
+    props.LateReverbPan   = {0.0f, 0.0f, 0.0f};
+    props.EchoTime  = 0.25f;
+    props.EchoDepth = 0.0f;
+    props.ModulationTime  = 0.25f;
+    props.ModulationDepth = 0.0f;
+    props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+    props.HFReference = 5000.0f;
+    props.LFReference = 250.0f;
+    props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+    props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
+    return props;
+}
+
+} // namespace
+
+const EffectProps ReverbEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(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"};
-        props->Reverb.DecayHFLimit = val != AL_FALSE;
+        props.DecayHFLimit = val != AL_FALSE;
         break;
 
     default:
@@ -35,2508 +107,1146 @@ void Reverb_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Reverb_setParami(props, param, vals[0]); }
-void Reverb_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, *vals); }
+void EffectHandler::SetParamf(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"};
-        props->Reverb.Density = val;
+        props.Density = val;
         break;
 
     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"};
-        props->Reverb.Diffusion = val;
+        props.Diffusion = val;
         break;
 
     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"};
-        props->Reverb.Gain = val;
+        props.Gain = val;
         break;
 
     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"};
-        props->Reverb.GainHF = val;
+        props.GainHF = val;
         break;
 
     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"};
-        props->Reverb.GainLF = val;
+        props.GainLF = val;
         break;
 
     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"};
-        props->Reverb.DecayTime = val;
+        props.DecayTime = val;
         break;
 
     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"};
-        props->Reverb.DecayHFRatio = val;
+        props.DecayHFRatio = val;
         break;
 
     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"};
-        props->Reverb.DecayLFRatio = val;
+        props.DecayLFRatio = val;
         break;
 
     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"};
-        props->Reverb.ReflectionsGain = val;
+        props.ReflectionsGain = val;
         break;
 
     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"};
-        props->Reverb.ReflectionsDelay = val;
+        props.ReflectionsDelay = val;
         break;
 
     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"};
-        props->Reverb.LateReverbGain = val;
+        props.LateReverbGain = val;
         break;
 
     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"};
-        props->Reverb.LateReverbDelay = val;
-        break;
-
-    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"};
-        props->Reverb.AirAbsorptionGainHF = val;
+        props.LateReverbDelay = val;
         break;
 
     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"};
-        props->Reverb.EchoTime = val;
+        props.EchoTime = val;
         break;
 
     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"};
-        props->Reverb.EchoDepth = val;
+        props.EchoDepth = val;
         break;
 
     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"};
-        props->Reverb.ModulationTime = val;
+        props.ModulationTime = val;
         break;
 
     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"};
-        props->Reverb.ModulationDepth = val;
+        props.ModulationDepth = val;
+        break;
+
+    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"};
+        props.AirAbsorptionGainHF = val;
         break;
 
     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"};
-        props->Reverb.HFReference = val;
+        props.HFReference = val;
         break;
 
     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"};
-        props->Reverb.LFReference = val;
+        props.LFReference = val;
         break;
 
     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"};
-        props->Reverb.RoomRolloffFactor = val;
+        props.RoomRolloffFactor = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
     }
 }
-void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals)
+void EffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals)
 {
+    static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); };
+    al::span<const float> values;
     switch(param)
     {
     case AL_EAXREVERB_REFLECTIONS_PAN:
-        if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+        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"};
-        props->Reverb.ReflectionsPan[0] = vals[0];
-        props->Reverb.ReflectionsPan[1] = vals[1];
-        props->Reverb.ReflectionsPan[2] = vals[2];
+        std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin());
         break;
     case AL_EAXREVERB_LATE_REVERB_PAN:
-        if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+        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"};
-        props->Reverb.LateReverbPan[0] = vals[0];
-        props->Reverb.LateReverbPan[1] = vals[1];
-        props->Reverb.LateReverbPan[2] = vals[2];
+        std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin());
         break;
 
     default:
-        Reverb_setParamf(props, param, vals[0]);
+        SetParamf(props, param, *vals);
         break;
     }
 }
 
-void Reverb_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_EAXREVERB_DECAY_HFLIMIT:
-        *val = props->Reverb.DecayHFLimit;
-        break;
-
+    case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break;
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
             param};
     }
 }
-void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Reverb_getParami(props, param, vals); }
-void Reverb_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_EAXREVERB_DENSITY:
-        *val = props->Reverb.Density;
-        break;
-
-    case AL_EAXREVERB_DIFFUSION:
-        *val = props->Reverb.Diffusion;
-        break;
-
-    case AL_EAXREVERB_GAIN:
-        *val = props->Reverb.Gain;
-        break;
-
-    case AL_EAXREVERB_GAINHF:
-        *val = props->Reverb.GainHF;
-        break;
-
-    case AL_EAXREVERB_GAINLF:
-        *val = props->Reverb.GainLF;
-        break;
-
-    case AL_EAXREVERB_DECAY_TIME:
-        *val = props->Reverb.DecayTime;
-        break;
-
-    case AL_EAXREVERB_DECAY_HFRATIO:
-        *val = props->Reverb.DecayHFRatio;
-        break;
-
-    case AL_EAXREVERB_DECAY_LFRATIO:
-        *val = props->Reverb.DecayLFRatio;
-        break;
-
-    case AL_EAXREVERB_REFLECTIONS_GAIN:
-        *val = props->Reverb.ReflectionsGain;
-        break;
-
-    case AL_EAXREVERB_REFLECTIONS_DELAY:
-        *val = props->Reverb.ReflectionsDelay;
-        break;
-
-    case AL_EAXREVERB_LATE_REVERB_GAIN:
-        *val = props->Reverb.LateReverbGain;
-        break;
-
-    case AL_EAXREVERB_LATE_REVERB_DELAY:
-        *val = props->Reverb.LateReverbDelay;
-        break;
-
-    case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
-        *val = props->Reverb.AirAbsorptionGainHF;
-        break;
-
-    case AL_EAXREVERB_ECHO_TIME:
-        *val = props->Reverb.EchoTime;
-        break;
-
-    case AL_EAXREVERB_ECHO_DEPTH:
-        *val = props->Reverb.EchoDepth;
-        break;
-
-    case AL_EAXREVERB_MODULATION_TIME:
-        *val = props->Reverb.ModulationTime;
-        break;
-
-    case AL_EAXREVERB_MODULATION_DEPTH:
-        *val = props->Reverb.ModulationDepth;
-        break;
-
-    case AL_EAXREVERB_HFREFERENCE:
-        *val = props->Reverb.HFReference;
-        break;
-
-    case AL_EAXREVERB_LFREFERENCE:
-        *val = props->Reverb.LFReference;
-        break;
-
-    case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
-        *val = props->Reverb.RoomRolloffFactor;
-        break;
+    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};
     }
 }
-void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals)
+void EffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals)
 {
+    al::span<float> values;
     switch(param)
     {
     case AL_EAXREVERB_REFLECTIONS_PAN:
-        vals[0] = props->Reverb.ReflectionsPan[0];
-        vals[1] = props->Reverb.ReflectionsPan[1];
-        vals[2] = props->Reverb.ReflectionsPan[2];
+        values = {vals, 3_uz};
+        std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin());
         break;
     case AL_EAXREVERB_LATE_REVERB_PAN:
-        vals[0] = props->Reverb.LateReverbPan[0];
-        vals[1] = props->Reverb.LateReverbPan[1];
-        vals[2] = props->Reverb.LateReverbPan[2];
+        values = {vals, 3_uz};
+        std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin());
         break;
 
     default:
-        Reverb_getParamf(props, param, vals);
+        GetParamf(props, param, vals);
         break;
     }
 }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Reverb.Density   = AL_EAXREVERB_DEFAULT_DENSITY;
-    props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
-    props.Reverb.Gain   = AL_EAXREVERB_DEFAULT_GAIN;
-    props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
-    props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
-    props.Reverb.DecayTime    = AL_EAXREVERB_DEFAULT_DECAY_TIME;
-    props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
-    props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
-    props.Reverb.ReflectionsGain   = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
-    props.Reverb.ReflectionsDelay  = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
-    props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
-    props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
-    props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
-    props.Reverb.LateReverbGain   = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
-    props.Reverb.LateReverbDelay  = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
-    props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
-    props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
-    props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
-    props.Reverb.EchoTime  = AL_EAXREVERB_DEFAULT_ECHO_TIME;
-    props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
-    props.Reverb.ModulationTime  = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
-    props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
-    props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
-    props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
-    props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
-    props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
-    props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
-    return props;
-}
 
+const EffectProps StdReverbEffectProps{genDefaultStdProps()};
 
-void StdReverb_setParami(EffectProps *props, ALenum param, int val)
+void EffectHandler::StdReverbSetParami(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, "Reverb decay hflimit out of range"};
-        props->Reverb.DecayHFLimit = val != AL_FALSE;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"};
+        props.DecayHFLimit = val != AL_FALSE;
         break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+            param};
     }
 }
-void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ StdReverb_setParami(props, param, vals[0]); }
-void StdReverb_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals)
+{ StdReverbSetParami(props, param, *vals); }
+void EffectHandler::StdReverbSetParamf(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, "Reverb density out of range"};
-        props->Reverb.Density = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"};
+        props.Density = val;
         break;
 
     case AL_REVERB_DIFFUSION:
         if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"};
-        props->Reverb.Diffusion = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"};
+        props.Diffusion = val;
         break;
 
     case AL_REVERB_GAIN:
         if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"};
-        props->Reverb.Gain = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"};
+        props.Gain = val;
         break;
 
     case AL_REVERB_GAINHF:
         if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"};
-        props->Reverb.GainHF = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"};
+        props.GainHF = val;
         break;
 
     case AL_REVERB_DECAY_TIME:
         if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"};
-        props->Reverb.DecayTime = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"};
+        props.DecayTime = val;
         break;
 
     case AL_REVERB_DECAY_HFRATIO:
         if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"};
-        props->Reverb.DecayHFRatio = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"};
+        props.DecayHFRatio = val;
         break;
 
     case AL_REVERB_REFLECTIONS_GAIN:
         if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"};
-        props->Reverb.ReflectionsGain = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"};
+        props.ReflectionsGain = val;
         break;
 
     case AL_REVERB_REFLECTIONS_DELAY:
         if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
-            throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"};
-        props->Reverb.ReflectionsDelay = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"};
+        props.ReflectionsDelay = val;
         break;
 
     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, "Reverb late reverb gain out of range"};
-        props->Reverb.LateReverbGain = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"};
+        props.LateReverbGain = val;
         break;
 
     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, "Reverb late reverb delay out of range"};
-        props->Reverb.LateReverbDelay = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"};
+        props.LateReverbDelay = val;
         break;
 
     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, "Reverb air absorption gainhf out of range"};
-        props->Reverb.AirAbsorptionGainHF = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"};
+        props.AirAbsorptionGainHF = val;
         break;
 
     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, "Reverb room rolloff factor out of range"};
-        props->Reverb.RoomRolloffFactor = val;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"};
+        props.RoomRolloffFactor = val;
         break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
     }
 }
-void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ StdReverb_setParamf(props, param, vals[0]); }
-
-void StdReverb_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals)
 {
     switch(param)
     {
-    case AL_REVERB_DECAY_HFLIMIT:
-        *val = props->Reverb.DecayHFLimit;
+    default:
+        StdReverbSetParamf(props, param, *vals);
         break;
+    }
+}
 
+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 reverb integer property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+            param};
     }
 }
-void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ StdReverb_getParami(props, param, vals); }
-void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals)
+{ StdReverbGetParami(props, param, vals); }
+void EffectHandler::StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_REVERB_DENSITY:
-        *val = props->Reverb.Density;
-        break;
+    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;
 
-    case AL_REVERB_DIFFUSION:
-        *val = props->Reverb.Diffusion;
+    default:
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
+    }
+}
+void EffectHandler::StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals)
+{
+    switch(param)
+    {
+    default:
+        StdReverbGetParamf(props, param, vals);
         break;
+    }
+}
 
-    case AL_REVERB_GAIN:
-        *val = props->Reverb.Gain;
-        break;
 
-    case AL_REVERB_GAINHF:
-        *val = props->Reverb.GainHF;
-        break;
+#ifdef ALSOFT_EAX
+namespace {
 
-    case AL_REVERB_DECAY_TIME:
-        *val = props->Reverb.DecayTime;
-        break;
+class EaxReverbEffectException : public EaxException
+{
+public:
+    explicit EaxReverbEffectException(const char* message)
+        : EaxException{"EAX_REVERB_EFFECT", message}
+    {}
+}; // EaxReverbEffectException
 
-    case AL_REVERB_DECAY_HFRATIO:
-        *val = props->Reverb.DecayHFRatio;
-        break;
+struct EnvironmentValidator1 {
+    void operator()(unsigned long ulEnvironment) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Environment",
+            ulEnvironment,
+            EAXREVERB_MINENVIRONMENT,
+            EAX1REVERB_MAXENVIRONMENT);
+    }
+}; // EnvironmentValidator1
 
-    case AL_REVERB_REFLECTIONS_GAIN:
-        *val = props->Reverb.ReflectionsGain;
-        break;
+struct VolumeValidator {
+    void operator()(float volume) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Volume",
+            volume,
+            EAX1REVERB_MINVOLUME,
+            EAX1REVERB_MAXVOLUME);
+    }
+}; // VolumeValidator
 
-    case AL_REVERB_REFLECTIONS_DELAY:
-        *val = props->Reverb.ReflectionsDelay;
-        break;
+struct DecayTimeValidator {
+    void operator()(float flDecayTime) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Decay Time",
+            flDecayTime,
+            EAXREVERB_MINDECAYTIME,
+            EAXREVERB_MAXDECAYTIME);
+    }
+}; // DecayTimeValidator
 
-    case AL_REVERB_LATE_REVERB_GAIN:
-        *val = props->Reverb.LateReverbGain;
-        break;
+struct DampingValidator {
+    void operator()(float damping) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Damping",
+            damping,
+            EAX1REVERB_MINDAMPING,
+            EAX1REVERB_MAXDAMPING);
+    }
+}; // DampingValidator
 
-    case AL_REVERB_LATE_REVERB_DELAY:
-        *val = props->Reverb.LateReverbDelay;
-        break;
+struct AllValidator1 {
+    void operator()(const EAX_REVERBPROPERTIES& all) const
+    {
+        EnvironmentValidator1{}(all.environment);
+        VolumeValidator{}(all.fVolume);
+        DecayTimeValidator{}(all.fDecayTime_sec);
+        DampingValidator{}(all.fDamping);
+    }
+}; // AllValidator1
 
-    case AL_REVERB_AIR_ABSORPTION_GAINHF:
-        *val = props->Reverb.AirAbsorptionGainHF;
-        break;
+struct RoomValidator {
+    void operator()(long lRoom) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Room",
+            lRoom,
+            EAXREVERB_MINROOM,
+            EAXREVERB_MAXROOM);
+    }
+}; // RoomValidator
 
-    case AL_REVERB_ROOM_ROLLOFF_FACTOR:
-        *val = props->Reverb.RoomRolloffFactor;
-        break;
+struct RoomHFValidator {
+    void operator()(long lRoomHF) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Room HF",
+            lRoomHF,
+            EAXREVERB_MINROOMHF,
+            EAXREVERB_MAXROOMHF);
+    }
+}; // RoomHFValidator
 
-    default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
+struct RoomRolloffFactorValidator {
+    void operator()(float flRoomRolloffFactor) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Room Rolloff Factor",
+            flRoomRolloffFactor,
+            EAXREVERB_MINROOMROLLOFFFACTOR,
+            EAXREVERB_MAXROOMROLLOFFFACTOR);
     }
-}
-void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ StdReverb_getParamf(props, param, vals); }
+}; // RoomRolloffFactorValidator
 
-EffectProps genDefaultStdProps() noexcept
-{
-    EffectProps props{};
-    props.Reverb.Density   = AL_REVERB_DEFAULT_DENSITY;
-    props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
-    props.Reverb.Gain   = AL_REVERB_DEFAULT_GAIN;
-    props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
-    props.Reverb.GainLF = 1.0f;
-    props.Reverb.DecayTime    = AL_REVERB_DEFAULT_DECAY_TIME;
-    props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
-    props.Reverb.DecayLFRatio = 1.0f;
-    props.Reverb.ReflectionsGain   = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
-    props.Reverb.ReflectionsDelay  = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
-    props.Reverb.ReflectionsPan[0] = 0.0f;
-    props.Reverb.ReflectionsPan[1] = 0.0f;
-    props.Reverb.ReflectionsPan[2] = 0.0f;
-    props.Reverb.LateReverbGain   = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
-    props.Reverb.LateReverbDelay  = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
-    props.Reverb.LateReverbPan[0] = 0.0f;
-    props.Reverb.LateReverbPan[1] = 0.0f;
-    props.Reverb.LateReverbPan[2] = 0.0f;
-    props.Reverb.EchoTime  = 0.25f;
-    props.Reverb.EchoDepth = 0.0f;
-    props.Reverb.ModulationTime  = 0.25f;
-    props.Reverb.ModulationDepth = 0.0f;
-    props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
-    props.Reverb.HFReference = 5000.0f;
-    props.Reverb.LFReference = 250.0f;
-    props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
-    props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
-    return props;
-}
+struct DecayHFRatioValidator {
+    void operator()(float flDecayHFRatio) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Decay HF Ratio",
+            flDecayHFRatio,
+            EAXREVERB_MINDECAYHFRATIO,
+            EAXREVERB_MAXDECAYHFRATIO);
+    }
+}; // DecayHFRatioValidator
 
-} // namespace
+struct ReflectionsValidator {
+    void operator()(long lReflections) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Reflections",
+            lReflections,
+            EAXREVERB_MINREFLECTIONS,
+            EAXREVERB_MAXREFLECTIONS);
+    }
+}; // ReflectionsValidator
 
-DEFINE_ALEFFECT_VTABLE(Reverb);
+struct ReflectionsDelayValidator {
+    void operator()(float flReflectionsDelay) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Reflections Delay",
+            flReflectionsDelay,
+            EAXREVERB_MINREFLECTIONSDELAY,
+            EAXREVERB_MAXREFLECTIONSDELAY);
+    }
+}; // ReflectionsDelayValidator
 
-const EffectProps ReverbEffectProps{genDefaultProps()};
+struct ReverbValidator {
+    void operator()(long lReverb) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Reverb",
+            lReverb,
+            EAXREVERB_MINREVERB,
+            EAXREVERB_MAXREVERB);
+    }
+}; // ReverbValidator
 
-DEFINE_ALEFFECT_VTABLE(StdReverb);
+struct ReverbDelayValidator {
+    void operator()(float flReverbDelay) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Reverb Delay",
+            flReverbDelay,
+            EAXREVERB_MINREVERBDELAY,
+            EAXREVERB_MAXREVERBDELAY);
+    }
+}; // ReverbDelayValidator
 
-const EffectProps StdReverbEffectProps{genDefaultStdProps()};
+struct EnvironmentSizeValidator {
+    void operator()(float flEnvironmentSize) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Environment Size",
+            flEnvironmentSize,
+            EAXREVERB_MINENVIRONMENTSIZE,
+            EAXREVERB_MAXENVIRONMENTSIZE);
+    }
+}; // EnvironmentSizeValidator
 
-#ifdef ALSOFT_EAX
-namespace {
+struct EnvironmentDiffusionValidator {
+    void operator()(float flEnvironmentDiffusion) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Environment Diffusion",
+            flEnvironmentDiffusion,
+            EAXREVERB_MINENVIRONMENTDIFFUSION,
+            EAXREVERB_MAXENVIRONMENTDIFFUSION);
+    }
+}; // EnvironmentDiffusionValidator
 
-extern const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[];
+struct AirAbsorptionHFValidator {
+    void operator()(float flAirAbsorptionHF) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Air Absorbtion HF",
+            flAirAbsorptionHF,
+            EAXREVERB_MINAIRABSORPTIONHF,
+            EAXREVERB_MAXAIRABSORPTIONHF);
+    }
+}; // AirAbsorptionHFValidator
 
-using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t;
+struct FlagsValidator2 {
+    void operator()(unsigned long ulFlags) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Flags",
+            ulFlags,
+            0UL,
+            ~EAX2LISTENERFLAGS_RESERVED);
+    }
+}; // FlagsValidator2
 
-struct EaxReverbEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxReverbEffectDirtyFlagsValue ulEnvironment : 1;
-    EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1;
-    EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1;
-    EaxReverbEffectDirtyFlagsValue lRoom : 1;
-    EaxReverbEffectDirtyFlagsValue lRoomHF : 1;
-    EaxReverbEffectDirtyFlagsValue lRoomLF : 1;
-    EaxReverbEffectDirtyFlagsValue flDecayTime : 1;
-    EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1;
-    EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1;
-    EaxReverbEffectDirtyFlagsValue lReflections : 1;
-    EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1;
-    EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1;
-    EaxReverbEffectDirtyFlagsValue lReverb : 1;
-    EaxReverbEffectDirtyFlagsValue flReverbDelay : 1;
-    EaxReverbEffectDirtyFlagsValue vReverbPan : 1;
-    EaxReverbEffectDirtyFlagsValue flEchoTime : 1;
-    EaxReverbEffectDirtyFlagsValue flEchoDepth : 1;
-    EaxReverbEffectDirtyFlagsValue flModulationTime : 1;
-    EaxReverbEffectDirtyFlagsValue flModulationDepth : 1;
-    EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1;
-    EaxReverbEffectDirtyFlagsValue flHFReference : 1;
-    EaxReverbEffectDirtyFlagsValue flLFReference : 1;
-    EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1;
-    EaxReverbEffectDirtyFlagsValue ulFlags : 1;
-}; // EaxReverbEffectDirtyFlags
-
-struct Eax1ReverbEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
+struct AllValidator2 {
+    void operator()(const EAX20LISTENERPROPERTIES& all) const
+    {
+        RoomValidator{}(all.lRoom);
+        RoomHFValidator{}(all.lRoomHF);
+        RoomRolloffFactorValidator{}(all.flRoomRolloffFactor);
+        DecayTimeValidator{}(all.flDecayTime);
+        DecayHFRatioValidator{}(all.flDecayHFRatio);
+        ReflectionsValidator{}(all.lReflections);
+        ReflectionsDelayValidator{}(all.flReflectionsDelay);
+        ReverbValidator{}(all.lReverb);
+        ReverbDelayValidator{}(all.flReverbDelay);
+        EnvironmentValidator1{}(all.dwEnvironment);
+        EnvironmentSizeValidator{}(all.flEnvironmentSize);
+        EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion);
+        AirAbsorptionHFValidator{}(all.flAirAbsorptionHF);
+        FlagsValidator2{}(all.dwFlags);
+    }
+}; // AllValidator2
 
-    EaxReverbEffectDirtyFlagsValue ulEnvironment : 1;
-    EaxReverbEffectDirtyFlagsValue flVolume : 1;
-    EaxReverbEffectDirtyFlagsValue flDecayTime : 1;
-    EaxReverbEffectDirtyFlagsValue flDamping : 1;
-}; // Eax1ReverbEffectDirtyFlags
+struct EnvironmentValidator3 {
+    void operator()(unsigned long ulEnvironment) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Environment",
+            ulEnvironment,
+            EAXREVERB_MINENVIRONMENT,
+            EAX30REVERB_MAXENVIRONMENT);
+    }
+}; // EnvironmentValidator1
 
-class EaxReverbEffect final :
-    public EaxEffect
-{
-public:
-    EaxReverbEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAX_REVERBPROPERTIES eax1_{};
-    EAX_REVERBPROPERTIES eax1_d_{};
-    Eax1ReverbEffectDirtyFlags eax1_dirty_flags_{};
-    EAXREVERBPROPERTIES eax_{};
-    EAXREVERBPROPERTIES eax_d_{};
-    EaxReverbEffectDirtyFlags eax_dirty_flags_{};
-
-    [[noreturn]] static void eax_fail(const char* message);
-
-    void set_eax_defaults();
-
-    void set_efx_density_from_environment_size();
-    void set_efx_diffusion();
-    void set_efx_gain();
-    void set_efx_gain_hf();
-    void set_efx_gain_lf();
-    void set_efx_decay_time();
-    void set_efx_decay_hf_ratio();
-    void set_efx_decay_lf_ratio();
-    void set_efx_reflections_gain();
-    void set_efx_reflections_delay();
-    void set_efx_reflections_pan();
-    void set_efx_late_reverb_gain();
-    void set_efx_late_reverb_delay();
-    void set_efx_late_reverb_pan();
-    void set_efx_echo_time();
-    void set_efx_echo_depth();
-    void set_efx_modulation_time();
-    void set_efx_modulation_depth();
-    void set_efx_air_absorption_gain_hf();
-    void set_efx_hf_reference();
-    void set_efx_lf_reference();
-    void set_efx_room_rolloff_factor();
-    void set_efx_flags();
-    void set_efx_defaults();
-
-    void v1_get(const EaxEaxCall& eax_call) const;
-
-    void get_all(const EaxEaxCall& eax_call) const;
-
-    void get(const EaxEaxCall& eax_call) const;
-
-    static void v1_validate_environment(unsigned long environment);
-    static void v1_validate_volume(float volume);
-    static void v1_validate_decay_time(float decay_time);
-    static void v1_validate_damping(float damping);
-    static void v1_validate_all(const EAX_REVERBPROPERTIES& all);
-
-    void v1_defer_environment(unsigned long environment);
-    void v1_defer_volume(float volume);
-    void v1_defer_decay_time(float decay_time);
-    void v1_defer_damping(float damping);
-    void v1_defer_all(const EAX_REVERBPROPERTIES& all);
-
-    void v1_defer_environment(const EaxEaxCall& eax_call);
-    void v1_defer_volume(const EaxEaxCall& eax_call);
-    void v1_defer_decay_time(const EaxEaxCall& eax_call);
-    void v1_defer_damping(const EaxEaxCall& eax_call);
-    void v1_defer_all(const EaxEaxCall& eax_call);
-    void v1_defer(const EaxEaxCall& eax_call);
-
-    void v1_set_efx();
-
-    static void validate_environment(unsigned long ulEnvironment, int version, bool is_standalone);
-    static void validate_environment_size(float flEnvironmentSize);
-    static void validate_environment_diffusion(float flEnvironmentDiffusion);
-    static void validate_room(long lRoom);
-    static void validate_room_hf(long lRoomHF);
-    static void validate_room_lf(long lRoomLF);
-    static void validate_decay_time(float flDecayTime);
-    static void validate_decay_hf_ratio(float flDecayHFRatio);
-    static void validate_decay_lf_ratio(float flDecayLFRatio);
-    static void validate_reflections(long lReflections);
-    static void validate_reflections_delay(float flReflectionsDelay);
-    static void validate_reflections_pan(const EAXVECTOR& vReflectionsPan);
-    static void validate_reverb(long lReverb);
-    static void validate_reverb_delay(float flReverbDelay);
-    static void validate_reverb_pan(const EAXVECTOR& vReverbPan);
-    static void validate_echo_time(float flEchoTime);
-    static void validate_echo_depth(float flEchoDepth);
-    static void validate_modulation_time(float flModulationTime);
-    static void validate_modulation_depth(float flModulationDepth);
-    static void validate_air_absorbtion_hf(float air_absorbtion_hf);
-    static void validate_hf_reference(float flHFReference);
-    static void validate_lf_reference(float flLFReference);
-    static void validate_room_rolloff_factor(float flRoomRolloffFactor);
-    static void validate_flags(unsigned long ulFlags);
-    static void validate_all(const EAX20LISTENERPROPERTIES& all, int version);
-    static void validate_all(const EAXREVERBPROPERTIES& all, int version);
-
-    void defer_environment(unsigned long ulEnvironment);
-    void defer_environment_size(float flEnvironmentSize);
-    void defer_environment_diffusion(float flEnvironmentDiffusion);
-    void defer_room(long lRoom);
-    void defer_room_hf(long lRoomHF);
-    void defer_room_lf(long lRoomLF);
-    void defer_decay_time(float flDecayTime);
-    void defer_decay_hf_ratio(float flDecayHFRatio);
-    void defer_decay_lf_ratio(float flDecayLFRatio);
-    void defer_reflections(long lReflections);
-    void defer_reflections_delay(float flReflectionsDelay);
-    void defer_reflections_pan(const EAXVECTOR& vReflectionsPan);
-    void defer_reverb(long lReverb);
-    void defer_reverb_delay(float flReverbDelay);
-    void defer_reverb_pan(const EAXVECTOR& vReverbPan);
-    void defer_echo_time(float flEchoTime);
-    void defer_echo_depth(float flEchoDepth);
-    void defer_modulation_time(float flModulationTime);
-    void defer_modulation_depth(float flModulationDepth);
-    void defer_air_absorbtion_hf(float flAirAbsorptionHF);
-    void defer_hf_reference(float flHFReference);
-    void defer_lf_reference(float flLFReference);
-    void defer_room_rolloff_factor(float flRoomRolloffFactor);
-    void defer_flags(unsigned long ulFlags);
-    void defer_all(const EAX20LISTENERPROPERTIES& all);
-    void defer_all(const EAXREVERBPROPERTIES& all);
-
-    void defer_environment(const EaxEaxCall& eax_call);
-    void defer_environment_size(const EaxEaxCall& eax_call);
-    void defer_environment_diffusion(const EaxEaxCall& eax_call);
-    void defer_room(const EaxEaxCall& eax_call);
-    void defer_room_hf(const EaxEaxCall& eax_call);
-    void defer_room_lf(const EaxEaxCall& eax_call);
-    void defer_decay_time(const EaxEaxCall& eax_call);
-    void defer_decay_hf_ratio(const EaxEaxCall& eax_call);
-    void defer_decay_lf_ratio(const EaxEaxCall& eax_call);
-    void defer_reflections(const EaxEaxCall& eax_call);
-    void defer_reflections_delay(const EaxEaxCall& eax_call);
-    void defer_reflections_pan(const EaxEaxCall& eax_call);
-    void defer_reverb(const EaxEaxCall& eax_call);
-    void defer_reverb_delay(const EaxEaxCall& eax_call);
-    void defer_reverb_pan(const EaxEaxCall& eax_call);
-    void defer_echo_time(const EaxEaxCall& eax_call);
-    void defer_echo_depth(const EaxEaxCall& eax_call);
-    void defer_modulation_time(const EaxEaxCall& eax_call);
-    void defer_modulation_depth(const EaxEaxCall& eax_call);
-    void defer_air_absorbtion_hf(const EaxEaxCall& eax_call);
-    void defer_hf_reference(const EaxEaxCall& eax_call);
-    void defer_lf_reference(const EaxEaxCall& eax_call);
-    void defer_room_rolloff_factor(const EaxEaxCall& eax_call);
-    void defer_flags(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxReverbEffect
-
-
-class EaxReverbEffectException :
-    public EaxException
-{
-public:
-    explicit EaxReverbEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_REVERB_EFFECT", message}
+struct RoomLFValidator {
+    void operator()(long lRoomLF) const
     {
+        eax_validate_range<EaxReverbEffectException>(
+            "Room LF",
+            lRoomLF,
+            EAXREVERB_MINROOMLF,
+            EAXREVERB_MAXROOMLF);
     }
-}; // EaxReverbEffectException
+}; // RoomLFValidator
 
+struct DecayLFRatioValidator {
+    void operator()(float flDecayLFRatio) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Decay LF Ratio",
+            flDecayLFRatio,
+            EAXREVERB_MINDECAYLFRATIO,
+            EAXREVERB_MAXDECAYLFRATIO);
+    }
+}; // DecayLFRatioValidator
 
-EaxReverbEffect::EaxReverbEffect()
-    : EaxEffect{AL_EFFECT_EAXREVERB}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
+struct VectorValidator {
+    void operator()(const EAXVECTOR&) const
+    {}
+}; // VectorValidator
 
-void EaxReverbEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
+struct EchoTimeValidator {
+    void operator()(float flEchoTime) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Echo Time",
+            flEchoTime,
+            EAXREVERB_MINECHOTIME,
+            EAXREVERB_MAXECHOTIME);
+    }
+}; // EchoTimeValidator
 
-[[noreturn]] void EaxReverbEffect::eax_fail(const char* message)
-{
-    throw EaxReverbEffectException{message};
-}
+struct EchoDepthValidator {
+    void operator()(float flEchoDepth) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Echo Depth",
+            flEchoDepth,
+            EAXREVERB_MINECHODEPTH,
+            EAXREVERB_MAXECHODEPTH);
+    }
+}; // EchoDepthValidator
 
-void EaxReverbEffect::set_eax_defaults()
-{
-    eax1_ = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC];
-    eax1_d_ = eax1_;
-    eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC];
-    /* HACK: EAX2 has a default room volume of -10,000dB (silence), although
-     * newer versions use -1,000dB. What should be happening is properties for
-     * each EAX version is tracked separately, with the last version used for
-     * the properties to apply (presumably v2 or nothing being the default).
-     */
-    eax_.lRoom = EAXREVERB_MINROOM;
-    eax_d_ = eax_;
-}
+struct ModulationTimeValidator {
+    void operator()(float flModulationTime) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Modulation Time",
+            flModulationTime,
+            EAXREVERB_MINMODULATIONTIME,
+            EAXREVERB_MAXMODULATIONTIME);
+    }
+}; // ModulationTimeValidator
 
-void EaxReverbEffect::set_efx_density_from_environment_size()
-{
-    const auto eax_environment_size = eax_.flEnvironmentSize;
+struct ModulationDepthValidator {
+    void operator()(float flModulationDepth) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Modulation Depth",
+            flModulationDepth,
+            EAXREVERB_MINMODULATIONDEPTH,
+            EAXREVERB_MAXMODULATIONDEPTH);
+    }
+}; // ModulationDepthValidator
 
-    const auto efx_density = clamp(
-        (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F,
-        AL_EAXREVERB_MIN_DENSITY,
-        AL_EAXREVERB_MAX_DENSITY);
+struct HFReferenceValidator {
+    void operator()(float flHFReference) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "HF Reference",
+            flHFReference,
+            EAXREVERB_MINHFREFERENCE,
+            EAXREVERB_MAXHFREFERENCE);
+    }
+}; // HFReferenceValidator
 
-    al_effect_props_.Reverb.Density = efx_density;
-}
+struct LFReferenceValidator {
+    void operator()(float flLFReference) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "LF Reference",
+            flLFReference,
+            EAXREVERB_MINLFREFERENCE,
+            EAXREVERB_MAXLFREFERENCE);
+    }
+}; // LFReferenceValidator
 
-void EaxReverbEffect::set_efx_diffusion()
-{
-    const auto efx_diffusion = clamp(
-        eax_.flEnvironmentDiffusion,
-        AL_EAXREVERB_MIN_DIFFUSION,
-        AL_EAXREVERB_MAX_DIFFUSION);
+struct FlagsValidator3 {
+    void operator()(unsigned long ulFlags) const
+    {
+        eax_validate_range<EaxReverbEffectException>(
+            "Flags",
+            ulFlags,
+            0UL,
+            ~EAXREVERBFLAGS_RESERVED);
+    }
+}; // FlagsValidator3
 
-    al_effect_props_.Reverb.Diffusion = efx_diffusion;
-}
+struct AllValidator3 {
+    void operator()(const EAXREVERBPROPERTIES& all) const
+    {
+        EnvironmentValidator3{}(all.ulEnvironment);
+        EnvironmentSizeValidator{}(all.flEnvironmentSize);
+        EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion);
+        RoomValidator{}(all.lRoom);
+        RoomHFValidator{}(all.lRoomHF);
+        RoomLFValidator{}(all.lRoomLF);
+        DecayTimeValidator{}(all.flDecayTime);
+        DecayHFRatioValidator{}(all.flDecayHFRatio);
+        DecayLFRatioValidator{}(all.flDecayLFRatio);
+        ReflectionsValidator{}(all.lReflections);
+        ReflectionsDelayValidator{}(all.flReflectionsDelay);
+        VectorValidator{}(all.vReflectionsPan);
+        ReverbValidator{}(all.lReverb);
+        ReverbDelayValidator{}(all.flReverbDelay);
+        VectorValidator{}(all.vReverbPan);
+        EchoTimeValidator{}(all.flEchoTime);
+        EchoDepthValidator{}(all.flEchoDepth);
+        ModulationTimeValidator{}(all.flModulationTime);
+        ModulationDepthValidator{}(all.flModulationDepth);
+        AirAbsorptionHFValidator{}(all.flAirAbsorptionHF);
+        HFReferenceValidator{}(all.flHFReference);
+        LFReferenceValidator{}(all.flLFReference);
+        RoomRolloffFactorValidator{}(all.flRoomRolloffFactor);
+        FlagsValidator3{}(all.ulFlags);
+    }
+}; // AllValidator3
 
-void EaxReverbEffect::set_efx_gain()
-{
-    const auto efx_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lRoom)),
-        AL_EAXREVERB_MIN_GAIN,
-        AL_EAXREVERB_MAX_GAIN);
+struct EnvironmentDeferrer2 {
+    void operator()(EAX20LISTENERPROPERTIES& props, unsigned long dwEnvironment) const
+    {
+        props = EAX2REVERB_PRESETS[dwEnvironment];
+    }
+}; // EnvironmentDeferrer2
 
-    al_effect_props_.Reverb.Gain = efx_gain;
-}
+struct EnvironmentSizeDeferrer2 {
+    void operator()(EAX20LISTENERPROPERTIES& props, float flEnvironmentSize) const
+    {
+        if (props.flEnvironmentSize == flEnvironmentSize)
+        {
+            return;
+        }
 
-void EaxReverbEffect::set_efx_gain_hf()
-{
-    const auto efx_gain_hf = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lRoomHF)),
-        AL_EAXREVERB_MIN_GAINHF,
-        AL_EAXREVERB_MAX_GAINHF);
+        const auto scale = flEnvironmentSize / props.flEnvironmentSize;
+        props.flEnvironmentSize = flEnvironmentSize;
 
-    al_effect_props_.Reverb.GainHF = efx_gain_hf;
-}
-
-void EaxReverbEffect::set_efx_gain_lf()
-{
-    const auto efx_gain_lf = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lRoomLF)),
-        AL_EAXREVERB_MIN_GAINLF,
-        AL_EAXREVERB_MAX_GAINLF);
-
-    al_effect_props_.Reverb.GainLF = efx_gain_lf;
-}
-
-void EaxReverbEffect::set_efx_decay_time()
-{
-    const auto efx_decay_time = clamp(
-        eax_.flDecayTime,
-        AL_EAXREVERB_MIN_DECAY_TIME,
-        AL_EAXREVERB_MAX_DECAY_TIME);
-
-    al_effect_props_.Reverb.DecayTime = efx_decay_time;
-}
-
-void EaxReverbEffect::set_efx_decay_hf_ratio()
-{
-    const auto efx_decay_hf_ratio = clamp(
-        eax_.flDecayHFRatio,
-        AL_EAXREVERB_MIN_DECAY_HFRATIO,
-        AL_EAXREVERB_MAX_DECAY_HFRATIO);
-
-    al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio;
-}
-
-void EaxReverbEffect::set_efx_decay_lf_ratio()
-{
-    const auto efx_decay_lf_ratio = clamp(
-        eax_.flDecayLFRatio,
-        AL_EAXREVERB_MIN_DECAY_LFRATIO,
-        AL_EAXREVERB_MAX_DECAY_LFRATIO);
-
-    al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio;
-}
-
-void EaxReverbEffect::set_efx_reflections_gain()
-{
-    const auto efx_reflections_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lReflections)),
-        AL_EAXREVERB_MIN_REFLECTIONS_GAIN,
-        AL_EAXREVERB_MAX_REFLECTIONS_GAIN);
-
-    al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain;
-}
-
-void EaxReverbEffect::set_efx_reflections_delay()
-{
-    const auto efx_reflections_delay = clamp(
-        eax_.flReflectionsDelay,
-        AL_EAXREVERB_MIN_REFLECTIONS_DELAY,
-        AL_EAXREVERB_MAX_REFLECTIONS_DELAY);
-
-    al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay;
-}
-
-void EaxReverbEffect::set_efx_reflections_pan()
-{
-    al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x;
-    al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y;
-    al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z;
-}
-
-void EaxReverbEffect::set_efx_late_reverb_gain()
-{
-    const auto efx_late_reverb_gain = clamp(
-        level_mb_to_gain(static_cast<float>(eax_.lReverb)),
-        AL_EAXREVERB_MIN_LATE_REVERB_GAIN,
-        AL_EAXREVERB_MAX_LATE_REVERB_GAIN);
-
-    al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain;
-}
-
-void EaxReverbEffect::set_efx_late_reverb_delay()
-{
-    const auto efx_late_reverb_delay = clamp(
-        eax_.flReverbDelay,
-        AL_EAXREVERB_MIN_LATE_REVERB_DELAY,
-        AL_EAXREVERB_MAX_LATE_REVERB_DELAY);
-
-    al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay;
-}
-
-void EaxReverbEffect::set_efx_late_reverb_pan()
-{
-    al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x;
-    al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y;
-    al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z;
-}
-
-void EaxReverbEffect::set_efx_echo_time()
-{
-    const auto efx_echo_time = clamp(
-        eax_.flEchoTime,
-        AL_EAXREVERB_MIN_ECHO_TIME,
-        AL_EAXREVERB_MAX_ECHO_TIME);
-
-    al_effect_props_.Reverb.EchoTime = efx_echo_time;
-}
-
-void EaxReverbEffect::set_efx_echo_depth()
-{
-    const auto efx_echo_depth = clamp(
-        eax_.flEchoDepth,
-        AL_EAXREVERB_MIN_ECHO_DEPTH,
-        AL_EAXREVERB_MAX_ECHO_DEPTH);
-
-    al_effect_props_.Reverb.EchoDepth = efx_echo_depth;
-}
-
-void EaxReverbEffect::set_efx_modulation_time()
-{
-    const auto efx_modulation_time = clamp(
-        eax_.flModulationTime,
-        AL_EAXREVERB_MIN_MODULATION_TIME,
-        AL_EAXREVERB_MAX_MODULATION_TIME);
-
-    al_effect_props_.Reverb.ModulationTime = efx_modulation_time;
-}
-
-void EaxReverbEffect::set_efx_modulation_depth()
-{
-    const auto efx_modulation_depth = clamp(
-        eax_.flModulationDepth,
-        AL_EAXREVERB_MIN_MODULATION_DEPTH,
-        AL_EAXREVERB_MAX_MODULATION_DEPTH);
-
-    al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth;
-}
-
-void EaxReverbEffect::set_efx_air_absorption_gain_hf()
-{
-    const auto efx_air_absorption_hf = clamp(
-        level_mb_to_gain(eax_.flAirAbsorptionHF),
-        AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF,
-        AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF);
-
-    al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf;
-}
-
-void EaxReverbEffect::set_efx_hf_reference()
-{
-    const auto efx_hf_reference = clamp(
-        eax_.flHFReference,
-        AL_EAXREVERB_MIN_HFREFERENCE,
-        AL_EAXREVERB_MAX_HFREFERENCE);
-
-    al_effect_props_.Reverb.HFReference = efx_hf_reference;
-}
-
-void EaxReverbEffect::set_efx_lf_reference()
-{
-    const auto efx_lf_reference = clamp(
-        eax_.flLFReference,
-        AL_EAXREVERB_MIN_LFREFERENCE,
-        AL_EAXREVERB_MAX_LFREFERENCE);
-
-    al_effect_props_.Reverb.LFReference = efx_lf_reference;
-}
-
-void EaxReverbEffect::set_efx_room_rolloff_factor()
-{
-    const auto efx_room_rolloff_factor = clamp(
-        eax_.flRoomRolloffFactor,
-        AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR,
-        AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR);
-
-    al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor;
-}
-
-void EaxReverbEffect::set_efx_flags()
-{
-    al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
-}
-
-void EaxReverbEffect::set_efx_defaults()
-{
-    set_efx_density_from_environment_size();
-    set_efx_diffusion();
-    set_efx_gain();
-    set_efx_gain_hf();
-    set_efx_gain_lf();
-    set_efx_decay_time();
-    set_efx_decay_hf_ratio();
-    set_efx_decay_lf_ratio();
-    set_efx_reflections_gain();
-    set_efx_reflections_delay();
-    set_efx_reflections_pan();
-    set_efx_late_reverb_gain();
-    set_efx_late_reverb_delay();
-    set_efx_late_reverb_pan();
-    set_efx_echo_time();
-    set_efx_echo_depth();
-    set_efx_modulation_time();
-    set_efx_modulation_depth();
-    set_efx_air_absorption_gain_hf();
-    set_efx_hf_reference();
-    set_efx_lf_reference();
-    set_efx_room_rolloff_factor();
-    set_efx_flags();
-}
-
-void EaxReverbEffect::v1_get(const EaxEaxCall& eax_call) const
-{
-    switch(eax_call.get_property_id())
-    {
-        case DSPROPERTY_EAX_ALL:
-            eax_call.set_value<EaxReverbEffectException>(eax1_);
-            break;
-
-        case DSPROPERTY_EAX_ENVIRONMENT:
-            eax_call.set_value<EaxReverbEffectException>(eax1_.environment);
-            break;
-
-        case DSPROPERTY_EAX_VOLUME:
-            eax_call.set_value<EaxReverbEffectException>(eax1_.fVolume);
-            break;
-
-        case DSPROPERTY_EAX_DECAYTIME:
-            eax_call.set_value<EaxReverbEffectException>(eax1_.fDecayTime_sec);
-            break;
-
-        case DSPROPERTY_EAX_DAMPING:
-            eax_call.set_value<EaxReverbEffectException>(eax1_.fDamping);
-            break;
-
-        default:
-            eax_fail("Unsupported property id.");
-    }
-}
-
-void EaxReverbEffect::get_all(
-    const EaxEaxCall& eax_call) const
-{
-    if (eax_call.get_version() == 2)
-    {
-        auto& eax_reverb = eax_call.get_value<EaxReverbEffectException, EAX20LISTENERPROPERTIES>();
-        eax_reverb.lRoom = eax_.lRoom;
-        eax_reverb.lRoomHF = eax_.lRoomHF;
-        eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor;
-        eax_reverb.flDecayTime = eax_.flDecayTime;
-        eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio;
-        eax_reverb.lReflections = eax_.lReflections;
-        eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay;
-        eax_reverb.lReverb = eax_.lReverb;
-        eax_reverb.flReverbDelay = eax_.flReverbDelay;
-        eax_reverb.dwEnvironment = eax_.ulEnvironment;
-        eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize;
-        eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion;
-        eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF;
-        eax_reverb.dwFlags = eax_.ulFlags;
-    }
-    else
-    {
-        eax_call.set_value<EaxReverbEffectException>(eax_);
-    }
-}
-
-void EaxReverbEffect::get(const EaxEaxCall& eax_call) const
-{
-    if(eax_call.get_version() == 1)
-        v1_get(eax_call);
-    else switch(eax_call.get_property_id())
-    {
-        case EAXREVERB_NONE:
-            break;
-
-        case EAXREVERB_ALLPARAMETERS:
-            get_all(eax_call);
-            break;
-
-        case EAXREVERB_ENVIRONMENT:
-            eax_call.set_value<EaxReverbEffectException>(eax_.ulEnvironment);
-            break;
-
-        case EAXREVERB_ENVIRONMENTSIZE:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentSize);
-            break;
-
-        case EAXREVERB_ENVIRONMENTDIFFUSION:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentDiffusion);
-            break;
-
-        case EAXREVERB_ROOM:
-            eax_call.set_value<EaxReverbEffectException>(eax_.lRoom);
-            break;
-
-        case EAXREVERB_ROOMHF:
-            eax_call.set_value<EaxReverbEffectException>(eax_.lRoomHF);
-            break;
-
-        case EAXREVERB_ROOMLF:
-            eax_call.set_value<EaxReverbEffectException>(eax_.lRoomLF);
-            break;
-
-        case EAXREVERB_DECAYTIME:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flDecayTime);
-            break;
-
-        case EAXREVERB_DECAYHFRATIO:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flDecayHFRatio);
-            break;
-
-        case EAXREVERB_DECAYLFRATIO:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flDecayLFRatio);
-            break;
-
-        case EAXREVERB_REFLECTIONS:
-            eax_call.set_value<EaxReverbEffectException>(eax_.lReflections);
-            break;
-
-        case EAXREVERB_REFLECTIONSDELAY:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flReflectionsDelay);
-            break;
-
-        case EAXREVERB_REFLECTIONSPAN:
-            eax_call.set_value<EaxReverbEffectException>(eax_.vReflectionsPan);
-            break;
-
-        case EAXREVERB_REVERB:
-            eax_call.set_value<EaxReverbEffectException>(eax_.lReverb);
-            break;
-
-        case EAXREVERB_REVERBDELAY:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flReverbDelay);
-            break;
-
-        case EAXREVERB_REVERBPAN:
-            eax_call.set_value<EaxReverbEffectException>(eax_.vReverbPan);
-            break;
-
-        case EAXREVERB_ECHOTIME:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flEchoTime);
-            break;
-
-        case EAXREVERB_ECHODEPTH:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flEchoDepth);
-            break;
-
-        case EAXREVERB_MODULATIONTIME:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flModulationTime);
-            break;
-
-        case EAXREVERB_MODULATIONDEPTH:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flModulationDepth);
-            break;
-
-        case EAXREVERB_AIRABSORPTIONHF:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flAirAbsorptionHF);
-            break;
-
-        case EAXREVERB_HFREFERENCE:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flHFReference);
-            break;
-
-        case EAXREVERB_LFREFERENCE:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flLFReference);
-            break;
-
-        case EAXREVERB_ROOMROLLOFFFACTOR:
-            eax_call.set_value<EaxReverbEffectException>(eax_.flRoomRolloffFactor);
-            break;
-
-        case EAXREVERB_FLAGS:
-            eax_call.set_value<EaxReverbEffectException>(eax_.ulFlags);
-            break;
-
-        default:
-            eax_fail("Unsupported property id.");
-    }
-}
-
-void EaxReverbEffect::v1_validate_environment(unsigned long environment)
-{
-    validate_environment(environment, 1, true);
-}
-
-void EaxReverbEffect::v1_validate_volume(float volume)
-{
-    eax_validate_range<EaxReverbEffectException>("Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME);
-}
-
-void EaxReverbEffect::v1_validate_decay_time(float decay_time)
-{
-    validate_decay_time(decay_time);
-}
-
-void EaxReverbEffect::v1_validate_damping(float damping)
-{
-    eax_validate_range<EaxReverbEffectException>("Damping", damping, EAX1REVERB_MINDAMPING, EAX1REVERB_MAXDAMPING);
-}
-
-void EaxReverbEffect::v1_validate_all(const EAX_REVERBPROPERTIES& all)
-{
-    v1_validate_environment(all.environment);
-    v1_validate_volume(all.fVolume);
-    v1_validate_decay_time(all.fDecayTime_sec);
-    v1_validate_damping(all.fDamping);
-}
-
-void EaxReverbEffect::validate_environment(
-    unsigned long ulEnvironment,
-    int version,
-    bool is_standalone)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Environment",
-        ulEnvironment,
-        EAXREVERB_MINENVIRONMENT,
-        (version <= 2 || is_standalone) ? EAX1REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT);
-}
-
-void EaxReverbEffect::validate_environment_size(
-    float flEnvironmentSize)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Environment Size",
-        flEnvironmentSize,
-        EAXREVERB_MINENVIRONMENTSIZE,
-        EAXREVERB_MAXENVIRONMENTSIZE);
-}
-
-void EaxReverbEffect::validate_environment_diffusion(
-    float flEnvironmentDiffusion)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Environment Diffusion",
-        flEnvironmentDiffusion,
-        EAXREVERB_MINENVIRONMENTDIFFUSION,
-        EAXREVERB_MAXENVIRONMENTDIFFUSION);
-}
-
-void EaxReverbEffect::validate_room(
-    long lRoom)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Room",
-        lRoom,
-        EAXREVERB_MINROOM,
-        EAXREVERB_MAXROOM);
-}
-
-void EaxReverbEffect::validate_room_hf(
-    long lRoomHF)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Room HF",
-        lRoomHF,
-        EAXREVERB_MINROOMHF,
-        EAXREVERB_MAXROOMHF);
-}
-
-void EaxReverbEffect::validate_room_lf(
-    long lRoomLF)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Room LF",
-        lRoomLF,
-        EAXREVERB_MINROOMLF,
-        EAXREVERB_MAXROOMLF);
-}
-
-void EaxReverbEffect::validate_decay_time(
-    float flDecayTime)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Decay Time",
-        flDecayTime,
-        EAXREVERB_MINDECAYTIME,
-        EAXREVERB_MAXDECAYTIME);
-}
-
-void EaxReverbEffect::validate_decay_hf_ratio(
-    float flDecayHFRatio)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Decay HF Ratio",
-        flDecayHFRatio,
-        EAXREVERB_MINDECAYHFRATIO,
-        EAXREVERB_MAXDECAYHFRATIO);
-}
-
-void EaxReverbEffect::validate_decay_lf_ratio(
-    float flDecayLFRatio)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Decay LF Ratio",
-        flDecayLFRatio,
-        EAXREVERB_MINDECAYLFRATIO,
-        EAXREVERB_MAXDECAYLFRATIO);
-}
-
-void EaxReverbEffect::validate_reflections(
-    long lReflections)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Reflections",
-        lReflections,
-        EAXREVERB_MINREFLECTIONS,
-        EAXREVERB_MAXREFLECTIONS);
-}
-
-void EaxReverbEffect::validate_reflections_delay(
-    float flReflectionsDelay)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Reflections Delay",
-        flReflectionsDelay,
-        EAXREVERB_MINREFLECTIONSDELAY,
-        EAXREVERB_MAXREFLECTIONSDELAY);
-}
-
-void EaxReverbEffect::validate_reflections_pan(
-    const EAXVECTOR& vReflectionsPan)
-{
-    std::ignore = vReflectionsPan;
-}
-
-void EaxReverbEffect::validate_reverb(
-    long lReverb)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Reverb",
-        lReverb,
-        EAXREVERB_MINREVERB,
-        EAXREVERB_MAXREVERB);
-}
-
-void EaxReverbEffect::validate_reverb_delay(
-    float flReverbDelay)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Reverb Delay",
-        flReverbDelay,
-        EAXREVERB_MINREVERBDELAY,
-        EAXREVERB_MAXREVERBDELAY);
-}
-
-void EaxReverbEffect::validate_reverb_pan(
-    const EAXVECTOR& vReverbPan)
-{
-    std::ignore = vReverbPan;
-}
-
-void EaxReverbEffect::validate_echo_time(
-    float flEchoTime)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Echo Time",
-        flEchoTime,
-        EAXREVERB_MINECHOTIME,
-        EAXREVERB_MAXECHOTIME);
-}
-
-void EaxReverbEffect::validate_echo_depth(
-    float flEchoDepth)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Echo Depth",
-        flEchoDepth,
-        EAXREVERB_MINECHODEPTH,
-        EAXREVERB_MAXECHODEPTH);
-}
-
-void EaxReverbEffect::validate_modulation_time(
-    float flModulationTime)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Modulation Time",
-        flModulationTime,
-        EAXREVERB_MINMODULATIONTIME,
-        EAXREVERB_MAXMODULATIONTIME);
-}
-
-void EaxReverbEffect::validate_modulation_depth(
-    float flModulationDepth)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Modulation Depth",
-        flModulationDepth,
-        EAXREVERB_MINMODULATIONDEPTH,
-        EAXREVERB_MAXMODULATIONDEPTH);
-}
-
-void EaxReverbEffect::validate_air_absorbtion_hf(
-    float air_absorbtion_hf)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Air Absorbtion HF",
-        air_absorbtion_hf,
-        EAXREVERB_MINAIRABSORPTIONHF,
-        EAXREVERB_MAXAIRABSORPTIONHF);
-}
-
-void EaxReverbEffect::validate_hf_reference(
-    float flHFReference)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "HF Reference",
-        flHFReference,
-        EAXREVERB_MINHFREFERENCE,
-        EAXREVERB_MAXHFREFERENCE);
-}
-
-void EaxReverbEffect::validate_lf_reference(
-    float flLFReference)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "LF Reference",
-        flLFReference,
-        EAXREVERB_MINLFREFERENCE,
-        EAXREVERB_MAXLFREFERENCE);
-}
-
-void EaxReverbEffect::validate_room_rolloff_factor(
-    float flRoomRolloffFactor)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Room Rolloff Factor",
-        flRoomRolloffFactor,
-        EAXREVERB_MINROOMROLLOFFFACTOR,
-        EAXREVERB_MAXROOMROLLOFFFACTOR);
-}
-
-void EaxReverbEffect::validate_flags(
-    unsigned long ulFlags)
-{
-    eax_validate_range<EaxReverbEffectException>(
-        "Flags",
-        ulFlags,
-        0UL,
-        ~EAXREVERBFLAGS_RESERVED);
-}
-
-void EaxReverbEffect::validate_all(
-    const EAX20LISTENERPROPERTIES& listener,
-    int version)
-{
-    validate_room(listener.lRoom);
-    validate_room_hf(listener.lRoomHF);
-    validate_room_rolloff_factor(listener.flRoomRolloffFactor);
-    validate_decay_time(listener.flDecayTime);
-    validate_decay_hf_ratio(listener.flDecayHFRatio);
-    validate_reflections(listener.lReflections);
-    validate_reflections_delay(listener.flReflectionsDelay);
-    validate_reverb(listener.lReverb);
-    validate_reverb_delay(listener.flReverbDelay);
-    validate_environment(listener.dwEnvironment, version, false);
-    validate_environment_size(listener.flEnvironmentSize);
-    validate_environment_diffusion(listener.flEnvironmentDiffusion);
-    validate_air_absorbtion_hf(listener.flAirAbsorptionHF);
-    validate_flags(listener.dwFlags);
-}
-
-void EaxReverbEffect::validate_all(
-    const EAXREVERBPROPERTIES& lReverb,
-    int version)
-{
-    validate_environment(lReverb.ulEnvironment, version, false);
-    validate_environment_size(lReverb.flEnvironmentSize);
-    validate_environment_diffusion(lReverb.flEnvironmentDiffusion);
-    validate_room(lReverb.lRoom);
-    validate_room_hf(lReverb.lRoomHF);
-    validate_room_lf(lReverb.lRoomLF);
-    validate_decay_time(lReverb.flDecayTime);
-    validate_decay_hf_ratio(lReverb.flDecayHFRatio);
-    validate_decay_lf_ratio(lReverb.flDecayLFRatio);
-    validate_reflections(lReverb.lReflections);
-    validate_reflections_delay(lReverb.flReflectionsDelay);
-    validate_reverb(lReverb.lReverb);
-    validate_reverb_delay(lReverb.flReverbDelay);
-    validate_echo_time(lReverb.flEchoTime);
-    validate_echo_depth(lReverb.flEchoDepth);
-    validate_modulation_time(lReverb.flModulationTime);
-    validate_modulation_depth(lReverb.flModulationDepth);
-    validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF);
-    validate_hf_reference(lReverb.flHFReference);
-    validate_lf_reference(lReverb.flLFReference);
-    validate_room_rolloff_factor(lReverb.flRoomRolloffFactor);
-    validate_flags(lReverb.ulFlags);
-}
-
-void EaxReverbEffect::v1_defer_environment(unsigned long environment)
-{
-    eax1_d_ = EAX1REVERB_PRESETS[environment];
-    eax1_dirty_flags_.ulEnvironment = true;
-}
-
-void EaxReverbEffect::v1_defer_volume(float volume)
-{
-    eax1_d_.fVolume = volume;
-    eax1_dirty_flags_.flVolume = (eax1_.fVolume != eax1_d_.fVolume);
-}
-
-void EaxReverbEffect::v1_defer_decay_time(float decay_time)
-{
-    eax1_d_.fDecayTime_sec = decay_time;
-    eax1_dirty_flags_.flDecayTime = (eax1_.fDecayTime_sec != eax1_d_.fDecayTime_sec);
-}
-
-void EaxReverbEffect::v1_defer_damping(float damping)
-{
-    eax1_d_.fDamping = damping;
-    eax1_dirty_flags_.flDamping = (eax1_.fDamping != eax1_d_.fDamping);
-}
-
-void EaxReverbEffect::v1_defer_all(const EAX_REVERBPROPERTIES& lReverb)
-{
-    v1_defer_environment(lReverb.environment);
-    v1_defer_volume(lReverb.fVolume);
-    v1_defer_decay_time(lReverb.fDecayTime_sec);
-    v1_defer_damping(lReverb.fDamping);
-}
-
-
-void EaxReverbEffect::v1_set_efx()
-{
-    auto efx_props = eax_efx_reverb_presets[eax1_.environment];
-    efx_props.flGain = eax1_.fVolume;
-    efx_props.flDecayTime = eax1_.fDecayTime_sec;
-    efx_props.flDecayHFRatio = clamp(eax1_.fDamping, AL_EAXREVERB_MIN_DECAY_HFRATIO, AL_EAXREVERB_MAX_DECAY_HFRATIO);
-
-    al_effect_props_.Reverb.Density = efx_props.flDensity;
-    al_effect_props_.Reverb.Diffusion = efx_props.flDiffusion;
-    al_effect_props_.Reverb.Gain = efx_props.flGain;
-    al_effect_props_.Reverb.GainHF = efx_props.flGainHF;
-    al_effect_props_.Reverb.GainLF = efx_props.flGainLF;
-    al_effect_props_.Reverb.DecayTime = efx_props.flDecayTime;
-    al_effect_props_.Reverb.DecayHFRatio = efx_props.flDecayHFRatio;
-    al_effect_props_.Reverb.DecayLFRatio = efx_props.flDecayLFRatio;
-    al_effect_props_.Reverb.ReflectionsGain = efx_props.flReflectionsGain;
-    al_effect_props_.Reverb.ReflectionsDelay = efx_props.flReflectionsDelay;
-    al_effect_props_.Reverb.ReflectionsPan[0] = efx_props.flReflectionsPan[0];
-    al_effect_props_.Reverb.ReflectionsPan[1] = efx_props.flReflectionsPan[1];
-    al_effect_props_.Reverb.ReflectionsPan[2] = efx_props.flReflectionsPan[2];
-    al_effect_props_.Reverb.LateReverbGain = efx_props.flLateReverbGain;
-    al_effect_props_.Reverb.LateReverbDelay = efx_props.flLateReverbDelay;
-    al_effect_props_.Reverb.LateReverbPan[0] = efx_props.flLateReverbPan[0];
-    al_effect_props_.Reverb.LateReverbPan[1] = efx_props.flLateReverbPan[1];
-    al_effect_props_.Reverb.LateReverbPan[2] = efx_props.flLateReverbPan[2];
-    al_effect_props_.Reverb.EchoTime = efx_props.flEchoTime;
-    al_effect_props_.Reverb.EchoDepth = efx_props.flEchoDepth;
-    al_effect_props_.Reverb.ModulationTime = efx_props.flModulationTime;
-    al_effect_props_.Reverb.ModulationDepth = efx_props.flModulationDepth;
-    al_effect_props_.Reverb.HFReference = efx_props.flHFReference;
-    al_effect_props_.Reverb.LFReference = efx_props.flLFReference;
-    al_effect_props_.Reverb.RoomRolloffFactor = efx_props.flRoomRolloffFactor;
-    al_effect_props_.Reverb.AirAbsorptionGainHF = efx_props.flAirAbsorptionGainHF;
-    al_effect_props_.Reverb.DecayHFLimit = false;
-}
-
-void EaxReverbEffect::defer_environment(
-    unsigned long ulEnvironment)
-{
-    eax_d_.ulEnvironment = ulEnvironment;
-    eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment);
-}
-
-void EaxReverbEffect::defer_environment_size(
-    float flEnvironmentSize)
-{
-    eax_d_.flEnvironmentSize = flEnvironmentSize;
-    eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize);
-}
-
-void EaxReverbEffect::defer_environment_diffusion(
-    float flEnvironmentDiffusion)
-{
-    eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion;
-    eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion);
-}
-
-void EaxReverbEffect::defer_room(
-    long lRoom)
-{
-    eax_d_.lRoom = lRoom;
-    eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom);
-}
-
-void EaxReverbEffect::defer_room_hf(
-    long lRoomHF)
-{
-    eax_d_.lRoomHF = lRoomHF;
-    eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF);
-}
-
-void EaxReverbEffect::defer_room_lf(
-    long lRoomLF)
-{
-    eax_d_.lRoomLF = lRoomLF;
-    eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF);
-}
-
-void EaxReverbEffect::defer_decay_time(
-    float flDecayTime)
-{
-    eax_d_.flDecayTime = flDecayTime;
-    eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime);
-}
-
-void EaxReverbEffect::defer_decay_hf_ratio(
-    float flDecayHFRatio)
-{
-    eax_d_.flDecayHFRatio = flDecayHFRatio;
-    eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio);
-}
-
-void EaxReverbEffect::defer_decay_lf_ratio(
-    float flDecayLFRatio)
-{
-    eax_d_.flDecayLFRatio = flDecayLFRatio;
-    eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio);
-}
-
-void EaxReverbEffect::defer_reflections(
-    long lReflections)
-{
-    eax_d_.lReflections = lReflections;
-    eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections);
-}
-
-void EaxReverbEffect::defer_reflections_delay(
-    float flReflectionsDelay)
-{
-    eax_d_.flReflectionsDelay = flReflectionsDelay;
-    eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay);
-}
-
-void EaxReverbEffect::defer_reflections_pan(
-    const EAXVECTOR& vReflectionsPan)
-{
-    eax_d_.vReflectionsPan = vReflectionsPan;
-    eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan);
-}
-
-void EaxReverbEffect::defer_reverb(
-    long lReverb)
-{
-    eax_d_.lReverb = lReverb;
-    eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb);
-}
-
-void EaxReverbEffect::defer_reverb_delay(
-    float flReverbDelay)
-{
-    eax_d_.flReverbDelay = flReverbDelay;
-    eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay);
-}
-
-void EaxReverbEffect::defer_reverb_pan(
-    const EAXVECTOR& vReverbPan)
-{
-    eax_d_.vReverbPan = vReverbPan;
-    eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan);
-}
-
-void EaxReverbEffect::defer_echo_time(
-    float flEchoTime)
-{
-    eax_d_.flEchoTime = flEchoTime;
-    eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime);
-}
-
-void EaxReverbEffect::defer_echo_depth(
-    float flEchoDepth)
-{
-    eax_d_.flEchoDepth = flEchoDepth;
-    eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth);
-}
-
-void EaxReverbEffect::defer_modulation_time(
-    float flModulationTime)
-{
-    eax_d_.flModulationTime = flModulationTime;
-    eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime);
-}
-
-void EaxReverbEffect::defer_modulation_depth(
-    float flModulationDepth)
-{
-    eax_d_.flModulationDepth = flModulationDepth;
-    eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth);
-}
-
-void EaxReverbEffect::defer_air_absorbtion_hf(
-    float flAirAbsorptionHF)
-{
-    eax_d_.flAirAbsorptionHF = flAirAbsorptionHF;
-    eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF);
-}
-
-void EaxReverbEffect::defer_hf_reference(
-    float flHFReference)
-{
-    eax_d_.flHFReference = flHFReference;
-    eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference);
-}
-
-void EaxReverbEffect::defer_lf_reference(
-    float flLFReference)
-{
-    eax_d_.flLFReference = flLFReference;
-    eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference);
-}
-
-void EaxReverbEffect::defer_room_rolloff_factor(
-    float flRoomRolloffFactor)
-{
-    eax_d_.flRoomRolloffFactor = flRoomRolloffFactor;
-    eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor);
-}
-
-void EaxReverbEffect::defer_flags(
-    unsigned long ulFlags)
-{
-    eax_d_.ulFlags = ulFlags;
-    eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags);
-}
-
-void EaxReverbEffect::defer_all(
-    const EAX20LISTENERPROPERTIES& listener)
-{
-    defer_room(listener.lRoom);
-    defer_room_hf(listener.lRoomHF);
-    defer_room_rolloff_factor(listener.flRoomRolloffFactor);
-    defer_decay_time(listener.flDecayTime);
-    defer_decay_hf_ratio(listener.flDecayHFRatio);
-    defer_reflections(listener.lReflections);
-    defer_reflections_delay(listener.flReflectionsDelay);
-    defer_reverb(listener.lReverb);
-    defer_reverb_delay(listener.flReverbDelay);
-    defer_environment(listener.dwEnvironment);
-    defer_environment_size(listener.flEnvironmentSize);
-    defer_environment_diffusion(listener.flEnvironmentDiffusion);
-    defer_air_absorbtion_hf(listener.flAirAbsorptionHF);
-    defer_flags(listener.dwFlags);
-}
-
-void EaxReverbEffect::defer_all(
-    const EAXREVERBPROPERTIES& lReverb)
-{
-    defer_environment(lReverb.ulEnvironment);
-    defer_environment_size(lReverb.flEnvironmentSize);
-    defer_environment_diffusion(lReverb.flEnvironmentDiffusion);
-    defer_room(lReverb.lRoom);
-    defer_room_hf(lReverb.lRoomHF);
-    defer_room_lf(lReverb.lRoomLF);
-    defer_decay_time(lReverb.flDecayTime);
-    defer_decay_hf_ratio(lReverb.flDecayHFRatio);
-    defer_decay_lf_ratio(lReverb.flDecayLFRatio);
-    defer_reflections(lReverb.lReflections);
-    defer_reflections_delay(lReverb.flReflectionsDelay);
-    defer_reflections_pan(lReverb.vReflectionsPan);
-    defer_reverb(lReverb.lReverb);
-    defer_reverb_delay(lReverb.flReverbDelay);
-    defer_reverb_pan(lReverb.vReverbPan);
-    defer_echo_time(lReverb.flEchoTime);
-    defer_echo_depth(lReverb.flEchoDepth);
-    defer_modulation_time(lReverb.flModulationTime);
-    defer_modulation_depth(lReverb.flModulationDepth);
-    defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF);
-    defer_hf_reference(lReverb.flHFReference);
-    defer_lf_reference(lReverb.flLFReference);
-    defer_room_rolloff_factor(lReverb.flRoomRolloffFactor);
-    defer_flags(lReverb.ulFlags);
-}
-
-
-void EaxReverbEffect::v1_defer_environment(const EaxEaxCall& eax_call)
-{
-    const auto& environment = eax_call.get_value<EaxReverbEffectException,
-        const decltype(EAX_REVERBPROPERTIES::environment)>();
-
-    validate_environment(environment, 1, true);
-
-    const auto& reverb_preset = EAX1REVERB_PRESETS[environment];
-    v1_defer_all(reverb_preset);
-}
-
-void EaxReverbEffect::v1_defer_volume(const EaxEaxCall& eax_call)
-{
-    const auto& volume = eax_call.get_value<EaxReverbEffectException,
-        const decltype(EAX_REVERBPROPERTIES::fVolume)>();
-
-    v1_validate_volume(volume);
-    v1_defer_volume(volume);
-}
-
-void EaxReverbEffect::v1_defer_decay_time(const EaxEaxCall& eax_call)
-{
-    const auto& decay_time = eax_call.get_value<EaxReverbEffectException,
-        const decltype(EAX_REVERBPROPERTIES::fDecayTime_sec)>();
-
-    v1_validate_decay_time(decay_time);
-    v1_defer_decay_time(decay_time);
-}
-
-void EaxReverbEffect::v1_defer_damping(const EaxEaxCall& eax_call)
-{
-    const auto& damping = eax_call.get_value<EaxReverbEffectException,
-        const decltype(EAX_REVERBPROPERTIES::fDamping)>();
-
-    v1_validate_damping(damping);
-    v1_defer_damping(damping);
-}
-
-void EaxReverbEffect::v1_defer_all(const EaxEaxCall& eax_call)
-{
-    const auto& reverb_all = eax_call.get_value<EaxReverbEffectException,
-        const EAX_REVERBPROPERTIES>();
-
-    v1_validate_all(reverb_all);
-    v1_defer_all(reverb_all);
-}
-
-
-void EaxReverbEffect::defer_environment(
-    const EaxEaxCall& eax_call)
-{
-    const auto& ulEnvironment =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulEnvironment)>();
-
-    validate_environment(ulEnvironment, eax_call.get_version(), true);
-
-    if (eax_d_.ulEnvironment == ulEnvironment)
-    {
-        return;
-    }
-
-    const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment];
-
-    defer_all(reverb_preset);
-}
-
-void EaxReverbEffect::defer_environment_size(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flEnvironmentSize =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentSize)>();
-
-    validate_environment_size(flEnvironmentSize);
-
-    if (eax_d_.flEnvironmentSize == flEnvironmentSize)
-    {
-        return;
-    }
-
-    const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize;
-
-    defer_environment_size(flEnvironmentSize);
-
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0)
-    {
-        const auto flDecayTime = clamp(
-            scale * eax_d_.flDecayTime,
-            EAXREVERB_MINDECAYTIME,
-            EAXREVERB_MAXDECAYTIME);
-
-        defer_decay_time(flDecayTime);
-    }
+        if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0)
+        {
+            props.flDecayTime = std::clamp(
+                props.flDecayTime * scale,
+                EAXREVERB_MINDECAYTIME,
+                EAXREVERB_MAXDECAYTIME);
+        }
 
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0)
-    {
-        if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
+        if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 &&
+            (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0)
         {
-            const auto lReflections = clamp(
-                eax_d_.lReflections - static_cast<long>(gain_to_level_mb(scale)),
+            props.lReflections = std::clamp(
+                props.lReflections - static_cast<long>(gain_to_level_mb(scale)),
                 EAXREVERB_MINREFLECTIONS,
                 EAXREVERB_MAXREFLECTIONS);
-
-            defer_reflections(lReflections);
         }
-    }
-
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
-    {
-        const auto flReflectionsDelay = clamp(
-            eax_d_.flReflectionsDelay * scale,
-            EAXREVERB_MINREFLECTIONSDELAY,
-            EAXREVERB_MAXREFLECTIONSDELAY);
-
-        defer_reflections_delay(flReflectionsDelay);
-    }
-
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0)
-    {
-        const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F;
 
-        const auto lReverb = clamp(
-            eax_d_.lReverb - static_cast<long>(std::log10(scale) * log_scalar),
-            EAXREVERB_MINREVERB,
-            EAXREVERB_MAXREVERB);
+        if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0)
+        {
+            props.flReflectionsDelay = std::clamp(
+                props.flReflectionsDelay * scale,
+                EAXREVERB_MINREFLECTIONSDELAY,
+                EAXREVERB_MAXREFLECTIONSDELAY);
+        }
 
-        defer_reverb(lReverb);
-    }
+        if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBSCALE) != 0)
+        {
+            const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F;
 
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0)
-    {
-        const auto flReverbDelay = clamp(
-            scale * eax_d_.flReverbDelay,
-            EAXREVERB_MINREVERBDELAY,
-            EAXREVERB_MAXREVERBDELAY);
+            props.lReverb = std::clamp(
+                props.lReverb - static_cast<long>(std::log10(scale) * log_scalar),
+                EAXREVERB_MINREVERB,
+                EAXREVERB_MAXREVERB);
+        }
 
-        defer_reverb_delay(flReverbDelay);
+        if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0)
+        {
+            props.flReverbDelay = std::clamp(
+                props.flReverbDelay * scale,
+                EAXREVERB_MINREVERBDELAY,
+                EAXREVERB_MAXREVERBDELAY);
+        }
     }
+}; // EnvironmentSizeDeferrer2
 
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0)
+struct EnvironmentDeferrer3 {
+    void operator()(EAXREVERBPROPERTIES& props, unsigned long ulEnvironment) const
     {
-        const auto flEchoTime = clamp(
-            eax_d_.flEchoTime * scale,
-            EAXREVERB_MINECHOTIME,
-            EAXREVERB_MAXECHOTIME);
+        if (ulEnvironment == EAX_ENVIRONMENT_UNDEFINED)
+        {
+            props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED;
+            return;
+        }
 
-        defer_echo_time(flEchoTime);
+        props = EAXREVERB_PRESETS[ulEnvironment];
     }
+}; // EnvironmentDeferrer3
 
-    if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0)
+struct EnvironmentSizeDeferrer3 {
+    void operator()(EAXREVERBPROPERTIES& props, float flEnvironmentSize) const
     {
-        const auto flModulationTime = clamp(
-            scale * eax_d_.flModulationTime,
-            EAXREVERB_MINMODULATIONTIME,
-            EAXREVERB_MAXMODULATIONTIME);
-
-        defer_modulation_time(flModulationTime);
-    }
-}
-
-void EaxReverbEffect::defer_environment_diffusion(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flEnvironmentDiffusion =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentDiffusion)>();
-
-    validate_environment_diffusion(flEnvironmentDiffusion);
-    defer_environment_diffusion(flEnvironmentDiffusion);
-}
-
-void EaxReverbEffect::defer_room(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lRoom =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoom)>();
-
-    validate_room(lRoom);
-    defer_room(lRoom);
-}
-
-void EaxReverbEffect::defer_room_hf(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lRoomHF =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomHF)>();
-
-    validate_room_hf(lRoomHF);
-    defer_room_hf(lRoomHF);
-}
-
-void EaxReverbEffect::defer_room_lf(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lRoomLF =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomLF)>();
-
-    validate_room_lf(lRoomLF);
-    defer_room_lf(lRoomLF);
-}
+        if (props.flEnvironmentSize == flEnvironmentSize)
+        {
+            return;
+        }
 
-void EaxReverbEffect::defer_decay_time(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flDecayTime =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayTime)>();
+        const auto scale = flEnvironmentSize / props.flEnvironmentSize;
+        props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED;
+        props.flEnvironmentSize = flEnvironmentSize;
 
-    validate_decay_time(flDecayTime);
-    defer_decay_time(flDecayTime);
-}
+        if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0)
+        {
+            props.flDecayTime = std::clamp(
+                props.flDecayTime * scale,
+                EAXREVERB_MINDECAYTIME,
+                EAXREVERB_MAXDECAYTIME);
+        }
 
-void EaxReverbEffect::defer_decay_hf_ratio(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flDecayHFRatio =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayHFRatio)>();
+        if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 &&
+            (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
+        {
+            props.lReflections = std::clamp(
+                props.lReflections - static_cast<long>(gain_to_level_mb(scale)),
+                EAXREVERB_MINREFLECTIONS,
+                EAXREVERB_MAXREFLECTIONS);
+        }
 
-    validate_decay_hf_ratio(flDecayHFRatio);
-    defer_decay_hf_ratio(flDecayHFRatio);
-}
+        if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0)
+        {
+            props.flReflectionsDelay = std::clamp(
+                props.flReflectionsDelay * scale,
+                EAXREVERB_MINREFLECTIONSDELAY,
+                EAXREVERB_MAXREFLECTIONSDELAY);
+        }
 
-void EaxReverbEffect::defer_decay_lf_ratio(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flDecayLFRatio =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayLFRatio)>();
+        if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0)
+        {
+            const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F;
+            props.lReverb = std::clamp(
+                props.lReverb - static_cast<long>(std::log10(scale) * log_scalar),
+                EAXREVERB_MINREVERB,
+                EAXREVERB_MAXREVERB);
+        }
 
-    validate_decay_lf_ratio(flDecayLFRatio);
-    defer_decay_lf_ratio(flDecayLFRatio);
-}
+        if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0)
+        {
+            props.flReverbDelay = std::clamp(
+                props.flReverbDelay * scale,
+                EAXREVERB_MINREVERBDELAY,
+                EAXREVERB_MAXREVERBDELAY);
+        }
 
-void EaxReverbEffect::defer_reflections(
-    const EaxEaxCall& eax_call)
-{
-    const auto& lReflections =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReflections)>();
+        if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0)
+        {
+            props.flEchoTime = std::clamp(
+                props.flEchoTime * scale,
+                EAXREVERB_MINECHOTIME,
+                EAXREVERB_MAXECHOTIME);
+        }
 
-    validate_reflections(lReflections);
-    defer_reflections(lReflections);
-}
+        if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0)
+        {
+            props.flModulationTime = std::clamp(
+                props.flModulationTime * scale,
+                EAXREVERB_MINMODULATIONTIME,
+                EAXREVERB_MAXMODULATIONTIME);
+        }
+    }
+}; // EnvironmentSizeDeferrer3
 
-void EaxReverbEffect::defer_reflections_delay(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flReflectionsDelay =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReflectionsDelay)>();
+} // namespace
 
-    validate_reflections_delay(flReflectionsDelay);
-    defer_reflections_delay(flReflectionsDelay);
-}
 
-void EaxReverbEffect::defer_reflections_pan(
-    const EaxEaxCall& eax_call)
+struct EaxReverbCommitter::Exception : public EaxReverbEffectException
 {
-    const auto& vReflectionsPan =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReflectionsPan)>();
+    using EaxReverbEffectException::EaxReverbEffectException;
+};
 
-    validate_reflections_pan(vReflectionsPan);
-    defer_reflections_pan(vReflectionsPan);
-}
-
-void EaxReverbEffect::defer_reverb(
-    const EaxEaxCall& eax_call)
+[[noreturn]] void EaxReverbCommitter::fail(const char* message)
 {
-    const auto& lReverb =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReverb)>();
-
-    validate_reverb(lReverb);
-    defer_reverb(lReverb);
+    throw Exception{message};
 }
 
-void EaxReverbEffect::defer_reverb_delay(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept
 {
-    const auto& flReverbDelay =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReverbDelay)>();
-
-    validate_reverb_delay(flReverbDelay);
-    defer_reverb_delay(flReverbDelay);
+    assert(src.environment <= EAX1REVERB_MAXENVIRONMENT);
+    dst = EAXREVERB_PRESETS[src.environment];
+    dst.flDecayTime = src.fDecayTime_sec;
+    dst.flDecayHFRatio = src.fDamping;
+    dst.lReverb = static_cast<int>(std::min(gain_to_level_mb(src.fVolume), 0.0f));
 }
 
-void EaxReverbEffect::defer_reverb_pan(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept
 {
-    const auto& vReverbPan =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReverbPan)>();
-
-    validate_reverb_pan(vReverbPan);
-    defer_reverb_pan(vReverbPan);
+    assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT);
+    dst = EAXREVERB_PRESETS[src.dwEnvironment];
+    dst.ulEnvironment = src.dwEnvironment;
+    dst.flEnvironmentSize = src.flEnvironmentSize;
+    dst.flEnvironmentDiffusion = src.flEnvironmentDiffusion;
+    dst.lRoom = src.lRoom;
+    dst.lRoomHF = src.lRoomHF;
+    dst.flDecayTime = src.flDecayTime;
+    dst.flDecayHFRatio = src.flDecayHFRatio;
+    dst.lReflections = src.lReflections;
+    dst.flReflectionsDelay = src.flReflectionsDelay;
+    dst.lReverb = src.lReverb;
+    dst.flReverbDelay = src.flReverbDelay;
+    dst.flAirAbsorptionHF = src.flAirAbsorptionHF;
+    dst.flRoomRolloffFactor = src.flRoomRolloffFactor;
+    dst.ulFlags = src.dwFlags;
 }
 
-void EaxReverbEffect::defer_echo_time(
-    const EaxEaxCall& eax_call)
+bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props)
 {
-    const auto& flEchoTime =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoTime)>();
-
-    validate_echo_time(flEchoTime);
-    defer_echo_time(flEchoTime);
+    EAXREVERBPROPERTIES dst{};
+    translate(props, dst);
+    return commit(dst);
 }
 
-void EaxReverbEffect::defer_echo_depth(
-    const EaxEaxCall& eax_call)
+bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props)
 {
-    const auto& flEchoDepth =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoDepth)>();
-
-    validate_echo_depth(flEchoDepth);
-    defer_echo_depth(flEchoDepth);
+    EAXREVERBPROPERTIES dst{};
+    translate(props, dst);
+    return commit(dst);
 }
 
-void EaxReverbEffect::defer_modulation_time(
-    const EaxEaxCall& eax_call)
+bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props)
 {
-    const auto& flModulationTime =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationTime)>();
+    if(auto *cur = std::get_if<EAXREVERBPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-    validate_modulation_time(flModulationTime);
-    defer_modulation_time(flModulationTime);
-}
+    mEaxProps = props;
 
-void EaxReverbEffect::defer_modulation_depth(
-    const EaxEaxCall& eax_call)
-{
-    const auto& flModulationDepth =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationDepth)>();
+    const auto size = props.flEnvironmentSize;
+    const auto density = (size * size * size) / 16.0f;
+    mAlProps = [&]{
+        ReverbProps ret{};
+        ret.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY);
+        ret.Diffusion = props.flEnvironmentDiffusion;
+        ret.Gain = level_mb_to_gain(static_cast<float>(props.lRoom));
+        ret.GainHF = level_mb_to_gain(static_cast<float>(props.lRoomHF));
+        ret.GainLF = level_mb_to_gain(static_cast<float>(props.lRoomLF));
+        ret.DecayTime = props.flDecayTime;
+        ret.DecayHFRatio = props.flDecayHFRatio;
+        ret.DecayLFRatio = props.flDecayLFRatio;
+        ret.ReflectionsGain = level_mb_to_gain(static_cast<float>(props.lReflections));
+        ret.ReflectionsDelay = props.flReflectionsDelay;
+        ret.ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y,
+            props.vReflectionsPan.z};
+        ret.LateReverbGain = level_mb_to_gain(static_cast<float>(props.lReverb));
+        ret.LateReverbDelay = props.flReverbDelay;
+        ret.LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z};
+        ret.EchoTime = props.flEchoTime;
+        ret.EchoDepth = props.flEchoDepth;
+        ret.ModulationTime = props.flModulationTime;
+        ret.ModulationDepth = props.flModulationDepth;
+        ret.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF);
+        ret.HFReference = props.flHFReference;
+        ret.LFReference = props.flLFReference;
+        ret.RoomRolloffFactor = props.flRoomRolloffFactor;
+        ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
+        return ret;
+    }();
 
-    validate_modulation_depth(flModulationDepth);
-    defer_modulation_depth(flModulationDepth);
+    return true;
 }
 
-void EaxReverbEffect::defer_air_absorbtion_hf(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::SetDefaults(EAX_REVERBPROPERTIES &props)
 {
-    const auto& air_absorbtion_hf =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flAirAbsorptionHF)>();
-
-    validate_air_absorbtion_hf(air_absorbtion_hf);
-    defer_air_absorbtion_hf(air_absorbtion_hf);
+    props = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC];
 }
 
-void EaxReverbEffect::defer_hf_reference(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::SetDefaults(EAX20LISTENERPROPERTIES &props)
 {
-    const auto& flHFReference =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flHFReference)>();
-
-    validate_hf_reference(flHFReference);
-    defer_hf_reference(flHFReference);
+    props = EAX2REVERB_PRESETS[EAX2_ENVIRONMENT_GENERIC];
+    props.lRoom = -10'000L;
 }
 
-void EaxReverbEffect::defer_lf_reference(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props)
 {
-    const auto& flLFReference =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flLFReference)>();
-
-    validate_lf_reference(flLFReference);
-    defer_lf_reference(flLFReference);
+    props = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC];
 }
 
-void EaxReverbEffect::defer_room_rolloff_factor(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::SetDefaults(EaxEffectProps &props)
 {
-    const auto& flRoomRolloffFactor =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flRoomRolloffFactor)>();
-
-    validate_room_rolloff_factor(flRoomRolloffFactor);
-    defer_room_rolloff_factor(flRoomRolloffFactor);
+    SetDefaults(props.emplace<EAXREVERBPROPERTIES>());
 }
 
-void EaxReverbEffect::defer_flags(
-    const EaxEaxCall& eax_call)
-{
-    const auto& ulFlags =
-        eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulFlags)>();
-
-    validate_flags(ulFlags);
-    defer_flags(ulFlags);
-}
 
-void EaxReverbEffect::defer_all(
-    const EaxEaxCall& eax_call)
+void EaxReverbCommitter::Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props)
 {
-    const auto eax_version = eax_call.get_version();
-
-    if (eax_version == 2)
-    {
-        const auto& listener =
-            eax_call.get_value<EaxReverbEffectException, const EAX20LISTENERPROPERTIES>();
-
-        validate_all(listener, eax_version);
-        defer_all(listener);
-    }
-    else
+    switch(call.get_property_id())
     {
-        const auto& reverb_all =
-            eax_call.get_value<EaxReverbEffectException, const EAXREVERBPROPERTIES>();
-
-        validate_all(reverb_all, eax_version);
-        defer_all(reverb_all);
+    case DSPROPERTY_EAX_ALL: call.set_value<Exception>(props); break;
+    case DSPROPERTY_EAX_ENVIRONMENT: call.set_value<Exception>(props.environment); break;
+    case DSPROPERTY_EAX_VOLUME: call.set_value<Exception>(props.fVolume); break;
+    case DSPROPERTY_EAX_DECAYTIME: call.set_value<Exception>(props.fDecayTime_sec); break;
+    case DSPROPERTY_EAX_DAMPING: call.set_value<Exception>(props.fDamping); break;
+    default: fail_unknown_property_id();
     }
 }
 
-
-void EaxReverbEffect::v1_defer(const EaxEaxCall& eax_call)
+void EaxReverbCommitter::Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props)
 {
-    switch (eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case DSPROPERTY_EAX_ALL: return v1_defer_all(eax_call);
-        case DSPROPERTY_EAX_ENVIRONMENT: return v1_defer_environment(eax_call);
-        case DSPROPERTY_EAX_VOLUME: return v1_defer_volume(eax_call);
-        case DSPROPERTY_EAX_DECAYTIME: return v1_defer_decay_time(eax_call);
-        case DSPROPERTY_EAX_DAMPING: return v1_defer_damping(eax_call);
-        default: eax_fail("Unsupported property id.");
+    case DSPROPERTY_EAX20LISTENER_NONE: break;
+    case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case DSPROPERTY_EAX20LISTENER_ROOM: call.set_value<Exception>(props.lRoom); break;
+    case DSPROPERTY_EAX20LISTENER_ROOMHF: call.set_value<Exception>(props.lRoomHF); break;
+    case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: call.set_value<Exception>(props.flRoomRolloffFactor); break;
+    case DSPROPERTY_EAX20LISTENER_DECAYTIME: call.set_value<Exception>(props.flDecayTime); break;
+    case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: call.set_value<Exception>(props.flDecayHFRatio); break;
+    case DSPROPERTY_EAX20LISTENER_REFLECTIONS: call.set_value<Exception>(props.lReflections); break;
+    case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: call.set_value<Exception>(props.flReflectionsDelay); break;
+    case DSPROPERTY_EAX20LISTENER_REVERB: call.set_value<Exception>(props.lReverb); break;
+    case DSPROPERTY_EAX20LISTENER_REVERBDELAY: call.set_value<Exception>(props.flReverbDelay); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: call.set_value<Exception>(props.dwEnvironment); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: call.set_value<Exception>(props.flEnvironmentSize); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: call.set_value<Exception>(props.flEnvironmentDiffusion); break;
+    case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: call.set_value<Exception>(props.flAirAbsorptionHF); break;
+    case DSPROPERTY_EAX20LISTENER_FLAGS: call.set_value<Exception>(props.dwFlags); break;
+    default: fail_unknown_property_id();
     }
 }
 
-// [[nodiscard]]
-bool EaxReverbEffect::apply_deferred()
+void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &props)
 {
-    bool ret{false};
-
-    if(unlikely(eax1_dirty_flags_ != Eax1ReverbEffectDirtyFlags{}))
-    {
-        eax1_ = eax1_d_;
-
-        v1_set_efx();
-
-        eax1_dirty_flags_ = Eax1ReverbEffectDirtyFlags{};
-
-        ret = true;
-    }
-
-    if(eax_dirty_flags_ == EaxReverbEffectDirtyFlags{})
-        return ret;
-
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.ulEnvironment)
-    {
-    }
-
-    if (eax_dirty_flags_.flEnvironmentSize)
-    {
-        set_efx_density_from_environment_size();
-    }
-
-    if (eax_dirty_flags_.flEnvironmentDiffusion)
-    {
-        set_efx_diffusion();
-    }
-
-    if (eax_dirty_flags_.lRoom)
-    {
-        set_efx_gain();
-    }
-
-    if (eax_dirty_flags_.lRoomHF)
-    {
-        set_efx_gain_hf();
-    }
-
-    if (eax_dirty_flags_.lRoomLF)
-    {
-        set_efx_gain_lf();
-    }
-
-    if (eax_dirty_flags_.flDecayTime)
-    {
-        set_efx_decay_time();
-    }
-
-    if (eax_dirty_flags_.flDecayHFRatio)
-    {
-        set_efx_decay_hf_ratio();
-    }
-
-    if (eax_dirty_flags_.flDecayLFRatio)
-    {
-        set_efx_decay_lf_ratio();
-    }
-
-    if (eax_dirty_flags_.lReflections)
-    {
-        set_efx_reflections_gain();
-    }
-
-    if (eax_dirty_flags_.flReflectionsDelay)
-    {
-        set_efx_reflections_delay();
-    }
-
-    if (eax_dirty_flags_.vReflectionsPan)
-    {
-        set_efx_reflections_pan();
-    }
-
-    if (eax_dirty_flags_.lReverb)
-    {
-        set_efx_late_reverb_gain();
-    }
-
-    if (eax_dirty_flags_.flReverbDelay)
-    {
-        set_efx_late_reverb_delay();
-    }
-
-    if (eax_dirty_flags_.vReverbPan)
-    {
-        set_efx_late_reverb_pan();
-    }
-
-    if (eax_dirty_flags_.flEchoTime)
-    {
-        set_efx_echo_time();
-    }
-
-    if (eax_dirty_flags_.flEchoDepth)
-    {
-        set_efx_echo_depth();
-    }
-
-    if (eax_dirty_flags_.flModulationTime)
-    {
-        set_efx_modulation_time();
-    }
-
-    if (eax_dirty_flags_.flModulationDepth)
+    switch(call.get_property_id())
     {
-        set_efx_modulation_depth();
-    }
-
-    if (eax_dirty_flags_.flAirAbsorptionHF)
-    {
-        set_efx_air_absorption_gain_hf();
-    }
-
-    if (eax_dirty_flags_.flHFReference)
-    {
-        set_efx_hf_reference();
-    }
-
-    if (eax_dirty_flags_.flLFReference)
-    {
-        set_efx_lf_reference();
+    case EAXREVERB_NONE: break;
+    case EAXREVERB_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXREVERB_ENVIRONMENT: call.set_value<Exception>(props.ulEnvironment); break;
+    case EAXREVERB_ENVIRONMENTSIZE: call.set_value<Exception>(props.flEnvironmentSize); break;
+    case EAXREVERB_ENVIRONMENTDIFFUSION: call.set_value<Exception>(props.flEnvironmentDiffusion); break;
+    case EAXREVERB_ROOM: call.set_value<Exception>(props.lRoom); break;
+    case EAXREVERB_ROOMHF: call.set_value<Exception>(props.lRoomHF); break;
+    case EAXREVERB_ROOMLF: call.set_value<Exception>(props.lRoomLF); break;
+    case EAXREVERB_DECAYTIME: call.set_value<Exception>(props.flDecayTime); break;
+    case EAXREVERB_DECAYHFRATIO: call.set_value<Exception>(props.flDecayHFRatio); break;
+    case EAXREVERB_DECAYLFRATIO: call.set_value<Exception>(props.flDecayLFRatio); break;
+    case EAXREVERB_REFLECTIONS: call.set_value<Exception>(props.lReflections); break;
+    case EAXREVERB_REFLECTIONSDELAY: call.set_value<Exception>(props.flReflectionsDelay); break;
+    case EAXREVERB_REFLECTIONSPAN: call.set_value<Exception>(props.vReflectionsPan); break;
+    case EAXREVERB_REVERB: call.set_value<Exception>(props.lReverb); break;
+    case EAXREVERB_REVERBDELAY: call.set_value<Exception>(props.flReverbDelay); break;
+    case EAXREVERB_REVERBPAN: call.set_value<Exception>(props.vReverbPan); break;
+    case EAXREVERB_ECHOTIME: call.set_value<Exception>(props.flEchoTime); break;
+    case EAXREVERB_ECHODEPTH: call.set_value<Exception>(props.flEchoDepth); break;
+    case EAXREVERB_MODULATIONTIME: call.set_value<Exception>(props.flModulationTime); break;
+    case EAXREVERB_MODULATIONDEPTH: call.set_value<Exception>(props.flModulationDepth); break;
+    case EAXREVERB_AIRABSORPTIONHF: call.set_value<Exception>(props.flAirAbsorptionHF); break;
+    case EAXREVERB_HFREFERENCE: call.set_value<Exception>(props.flHFReference); break;
+    case EAXREVERB_LFREFERENCE: call.set_value<Exception>(props.flLFReference); break;
+    case EAXREVERB_ROOMROLLOFFFACTOR: call.set_value<Exception>(props.flRoomRolloffFactor); break;
+    case EAXREVERB_FLAGS: call.set_value<Exception>(props.ulFlags); break;
+    default: fail_unknown_property_id();
     }
+}
 
-    if (eax_dirty_flags_.flRoomRolloffFactor)
-    {
-        set_efx_room_rolloff_factor();
-    }
 
-    if (eax_dirty_flags_.ulFlags)
+void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props)
+{
+    switch(call.get_property_id())
     {
-        set_efx_flags();
+    case DSPROPERTY_EAX_ALL: defer<AllValidator1>(call, props); break;
+    case DSPROPERTY_EAX_ENVIRONMENT: defer<EnvironmentValidator1>(call, props.environment); break;
+    case DSPROPERTY_EAX_VOLUME: defer<VolumeValidator>(call, props.fVolume); break;
+    case DSPROPERTY_EAX_DECAYTIME: defer<DecayTimeValidator>(call, props.fDecayTime_sec); break;
+    case DSPROPERTY_EAX_DAMPING: defer<DampingValidator>(call, props.fDamping); break;
+    default: fail_unknown_property_id();
     }
-
-    eax_dirty_flags_ = EaxReverbEffectDirtyFlags{};
-
-    return true;
 }
 
-void EaxReverbEffect::set(const EaxEaxCall& eax_call)
+void EaxReverbCommitter::Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props)
 {
-    if(eax_call.get_version() == 1)
-        v1_defer(eax_call);
-    else switch(eax_call.get_property_id())
+    switch(call.get_property_id())
     {
-        case EAXREVERB_NONE:
-            break;
-
-        case EAXREVERB_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXREVERB_ENVIRONMENT:
-            defer_environment(eax_call);
-            break;
-
-        case EAXREVERB_ENVIRONMENTSIZE:
-            defer_environment_size(eax_call);
-            break;
-
-        case EAXREVERB_ENVIRONMENTDIFFUSION:
-            defer_environment_diffusion(eax_call);
-            break;
-
-        case EAXREVERB_ROOM:
-            defer_room(eax_call);
-            break;
-
-        case EAXREVERB_ROOMHF:
-            defer_room_hf(eax_call);
-            break;
-
-        case EAXREVERB_ROOMLF:
-            defer_room_lf(eax_call);
-            break;
-
-        case EAXREVERB_DECAYTIME:
-            defer_decay_time(eax_call);
-            break;
-
-        case EAXREVERB_DECAYHFRATIO:
-            defer_decay_hf_ratio(eax_call);
-            break;
-
-        case EAXREVERB_DECAYLFRATIO:
-            defer_decay_lf_ratio(eax_call);
-            break;
-
-        case EAXREVERB_REFLECTIONS:
-            defer_reflections(eax_call);
-            break;
-
-        case EAXREVERB_REFLECTIONSDELAY:
-            defer_reflections_delay(eax_call);
-            break;
-
-        case EAXREVERB_REFLECTIONSPAN:
-            defer_reflections_pan(eax_call);
-            break;
-
-        case EAXREVERB_REVERB:
-            defer_reverb(eax_call);
-            break;
-
-        case EAXREVERB_REVERBDELAY:
-            defer_reverb_delay(eax_call);
-            break;
-
-        case EAXREVERB_REVERBPAN:
-            defer_reverb_pan(eax_call);
-            break;
-
-        case EAXREVERB_ECHOTIME:
-            defer_echo_time(eax_call);
-            break;
-
-        case EAXREVERB_ECHODEPTH:
-            defer_echo_depth(eax_call);
-            break;
-
-        case EAXREVERB_MODULATIONTIME:
-            defer_modulation_time(eax_call);
-            break;
-
-        case EAXREVERB_MODULATIONDEPTH:
-            defer_modulation_depth(eax_call);
-            break;
-
-        case EAXREVERB_AIRABSORPTIONHF:
-            defer_air_absorbtion_hf(eax_call);
-            break;
-
-        case EAXREVERB_HFREFERENCE:
-            defer_hf_reference(eax_call);
-            break;
-
-        case EAXREVERB_LFREFERENCE:
-            defer_lf_reference(eax_call);
-            break;
-
-        case EAXREVERB_ROOMROLLOFFFACTOR:
-            defer_room_rolloff_factor(eax_call);
-            break;
-
-        case EAXREVERB_FLAGS:
-            defer_flags(eax_call);
-            break;
-
-        default:
-            eax_fail("Unsupported property id.");
+    case DSPROPERTY_EAX20LISTENER_NONE: break;
+    case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: defer<AllValidator2>(call, props); break;
+    case DSPROPERTY_EAX20LISTENER_ROOM: defer<RoomValidator>(call, props.lRoom); break;
+    case DSPROPERTY_EAX20LISTENER_ROOMHF: defer<RoomHFValidator>(call, props.lRoomHF); break;
+    case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: defer<RoomRolloffFactorValidator>(call, props.flRoomRolloffFactor); break;
+    case DSPROPERTY_EAX20LISTENER_DECAYTIME: defer<DecayTimeValidator>(call, props.flDecayTime); break;
+    case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: defer<DecayHFRatioValidator>(call, props.flDecayHFRatio); break;
+    case DSPROPERTY_EAX20LISTENER_REFLECTIONS: defer<ReflectionsValidator>(call, props.lReflections); break;
+    case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: defer<ReflectionsDelayValidator>(call, props.flReverbDelay); break;
+    case DSPROPERTY_EAX20LISTENER_REVERB: defer<ReverbValidator>(call, props.lReverb); break;
+    case DSPROPERTY_EAX20LISTENER_REVERBDELAY: defer<ReverbDelayValidator>(call, props.flReverbDelay); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: defer<EnvironmentValidator1, EnvironmentDeferrer2>(call, props, props.dwEnvironment); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: defer<EnvironmentSizeValidator, EnvironmentSizeDeferrer2>(call, props, props.flEnvironmentSize); break;
+    case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: defer<EnvironmentDiffusionValidator>(call, props.flEnvironmentDiffusion); break;
+    case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: defer<AirAbsorptionHFValidator>(call, props.flAirAbsorptionHF); break;
+    case DSPROPERTY_EAX20LISTENER_FLAGS: defer<FlagsValidator2>(call, props.dwFlags); break;
+    default: fail_unknown_property_id();
     }
 }
 
-const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[EAX1_ENVIRONMENT_COUNT] =
-{
-    EFX_REVERB_PRESET_GENERIC,
-    EFX_REVERB_PRESET_PADDEDCELL,
-    EFX_REVERB_PRESET_ROOM,
-    EFX_REVERB_PRESET_BATHROOM,
-    EFX_REVERB_PRESET_LIVINGROOM,
-    EFX_REVERB_PRESET_STONEROOM,
-    EFX_REVERB_PRESET_AUDITORIUM,
-    EFX_REVERB_PRESET_CONCERTHALL,
-    EFX_REVERB_PRESET_CAVE,
-    EFX_REVERB_PRESET_ARENA,
-    EFX_REVERB_PRESET_HANGAR,
-    EFX_REVERB_PRESET_CARPETEDHALLWAY,
-    EFX_REVERB_PRESET_HALLWAY,
-    EFX_REVERB_PRESET_STONECORRIDOR,
-    EFX_REVERB_PRESET_ALLEY,
-    EFX_REVERB_PRESET_FOREST,
-    EFX_REVERB_PRESET_CITY,
-    EFX_REVERB_PRESET_MOUNTAINS,
-    EFX_REVERB_PRESET_QUARRY,
-    EFX_REVERB_PRESET_PLAIN,
-    EFX_REVERB_PRESET_PARKINGLOT,
-    EFX_REVERB_PRESET_SEWERPIPE,
-    EFX_REVERB_PRESET_UNDERWATER,
-    EFX_REVERB_PRESET_DRUGGED,
-    EFX_REVERB_PRESET_DIZZY,
-    EFX_REVERB_PRESET_PSYCHOTIC,
-}; // EFXEAXREVERBPROPERTIES
-
-} // namespace
-
-EaxEffectUPtr eax_create_eax_reverb_effect()
+void EaxReverbCommitter::Set(const EaxCall &call, EAXREVERBPROPERTIES &props)
 {
-    return std::make_unique<EaxReverbEffect>();
+    switch(call.get_property_id())
+    {
+    case EAXREVERB_NONE: break;
+    case EAXREVERB_ALLPARAMETERS: defer<AllValidator3>(call, props); break;
+    case EAXREVERB_ENVIRONMENT: defer<EnvironmentValidator3, EnvironmentDeferrer3>(call, props, props.ulEnvironment); break;
+    case EAXREVERB_ENVIRONMENTSIZE: defer<EnvironmentSizeValidator, EnvironmentSizeDeferrer3>(call, props, props.flEnvironmentSize); break;
+    case EAXREVERB_ENVIRONMENTDIFFUSION: defer3<EnvironmentDiffusionValidator>(call, props, props.flEnvironmentDiffusion); break;
+    case EAXREVERB_ROOM: defer3<RoomValidator>(call, props, props.lRoom); break;
+    case EAXREVERB_ROOMHF: defer3<RoomHFValidator>(call, props, props.lRoomHF); break;
+    case EAXREVERB_ROOMLF: defer3<RoomLFValidator>(call, props, props.lRoomLF); break;
+    case EAXREVERB_DECAYTIME: defer3<DecayTimeValidator>(call, props, props.flDecayTime); break;
+    case EAXREVERB_DECAYHFRATIO: defer3<DecayHFRatioValidator>(call, props, props.flDecayHFRatio); break;
+    case EAXREVERB_DECAYLFRATIO: defer3<DecayLFRatioValidator>(call, props, props.flDecayLFRatio); break;
+    case EAXREVERB_REFLECTIONS: defer3<ReflectionsValidator>(call, props, props.lReflections); break;
+    case EAXREVERB_REFLECTIONSDELAY: defer3<ReflectionsDelayValidator>(call, props, props.flReflectionsDelay); break;
+    case EAXREVERB_REFLECTIONSPAN: defer3<VectorValidator>(call, props, props.vReflectionsPan); break;
+    case EAXREVERB_REVERB: defer3<ReverbValidator>(call, props, props.lReverb); break;
+    case EAXREVERB_REVERBDELAY: defer3<ReverbDelayValidator>(call, props, props.flReverbDelay); break;
+    case EAXREVERB_REVERBPAN: defer3<VectorValidator>(call, props, props.vReverbPan); break;
+    case EAXREVERB_ECHOTIME: defer3<EchoTimeValidator>(call, props, props.flEchoTime); break;
+    case EAXREVERB_ECHODEPTH: defer3<EchoDepthValidator>(call, props, props.flEchoDepth); break;
+    case EAXREVERB_MODULATIONTIME: defer3<ModulationTimeValidator>(call, props, props.flModulationTime); break;
+    case EAXREVERB_MODULATIONDEPTH: defer3<ModulationDepthValidator>(call, props, props.flModulationDepth); break;
+    case EAXREVERB_AIRABSORPTIONHF: defer3<AirAbsorptionHFValidator>(call, props, props.flAirAbsorptionHF); break;
+    case EAXREVERB_HFREFERENCE: defer3<HFReferenceValidator>(call, props, props.flHFReference); break;
+    case EAXREVERB_LFREFERENCE: defer3<LFReferenceValidator>(call, props, props.flLFReference); break;
+    case EAXREVERB_ROOMROLLOFFFACTOR: defer3<RoomRolloffFactorValidator>(call, props, props.flRoomRolloffFactor); break;
+    case EAXREVERB_FLAGS: defer3<FlagsValidator3>(call, props, props.ulFlags); break;
+    default: fail_unknown_property_id();
+    }
 }
 
 #endif // ALSOFT_EAX

+ 224 - 553
libs/openal-soft/al/effects/vmorpher.cpp

@@ -1,31 +1,30 @@
 
 #include "config.h"
 
+#include <optional>
 #include <stdexcept>
 
 #include "AL/al.h"
 #include "AL/efx.h"
 
 #include "alc/effects/base.h"
-#include "aloptional.h"
 #include "effects.h"
 
 #ifdef ALSOFT_EAX
 #include <cassert>
-
 #include "alnumeric.h"
-
-#include "al/eax_exception.h"
-#include "al/eax_utils.h"
+#include "al/eax/effect.h"
+#include "al/eax/exception.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 
 namespace {
 
-al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
+constexpr std::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val) noexcept
 {
 #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x:                \
-    return al::make_optional(VMorpherPhenome::x)
+    return VMorpherPhenome::x
     switch(val)
     {
     HANDLE_PHENOME(A);
@@ -59,10 +58,10 @@ al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
     HANDLE_PHENOME(V);
     HANDLE_PHENOME(Z);
     }
-    return al::nullopt;
+    return std::nullopt;
 #undef HANDLE_PHENOME
 }
-ALenum EnumFromPhenome(VMorpherPhenome phenome)
+constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome)
 {
 #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x
     switch(phenome)
@@ -102,17 +101,17 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome)
 #undef HANDLE_PHENOME
 }
 
-al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value)
+constexpr std::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) noexcept
 {
     switch(value)
     {
-    case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return al::make_optional(VMorpherWaveform::Sinusoid);
-    case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return al::make_optional(VMorpherWaveform::Triangle);
-    case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return al::make_optional(VMorpherWaveform::Sawtooth);
+    case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return VMorpherWaveform::Sinusoid;
+    case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle;
+    case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth;
     }
-    return al::nullopt;
+    return std::nullopt;
 }
-ALenum EnumFromWaveform(VMorpherWaveform type)
+constexpr ALenum EnumFromWaveform(VMorpherWaveform type)
 {
     switch(type)
     {
@@ -124,13 +123,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type)
         std::to_string(static_cast<int>(type))};
 }
 
-void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
+constexpr EffectProps genDefaultProps() noexcept
+{
+    VmorpherProps props{};
+    props.Rate                 = AL_VOCAL_MORPHER_DEFAULT_RATE;
+    props.PhonemeA             = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value();
+    props.PhonemeB             = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value();
+    props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
+    props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
+    props.Waveform             = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value();
+    return props;
+}
+
+} // namespace
+
+const EffectProps VmorpherEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(VmorpherProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_VOCAL_MORPHER_PHONEMEA:
         if(auto phenomeopt = PhenomeFromEnum(val))
-            props->Vmorpher.PhonemeA = *phenomeopt;
+            props.PhonemeA = *phenomeopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val};
         break;
@@ -138,12 +153,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
     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"};
-        props->Vmorpher.PhonemeACoarseTuning = val;
+        props.PhonemeACoarseTuning = val;
         break;
 
     case AL_VOCAL_MORPHER_PHONEMEB:
         if(auto phenomeopt = PhenomeFromEnum(val))
-            props->Vmorpher.PhonemeB = *phenomeopt;
+            props.PhonemeB = *phenomeopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val};
         break;
@@ -151,12 +166,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
     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"};
-        props->Vmorpher.PhonemeBCoarseTuning = val;
+        props.PhonemeBCoarseTuning = val;
         break;
 
     case AL_VOCAL_MORPHER_WAVEFORM:
         if(auto formopt = WaveformFromEmum(val))
-            props->Vmorpher.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val};
         break;
@@ -166,19 +181,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
         param};
 }
-void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(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"};
-        props->Vmorpher.Rate = val;
+        props.Rate = val;
         break;
 
     default:
@@ -186,49 +201,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
             param};
     }
 }
-void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Vmorpher_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, *vals); }
 
-void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val)
+void EffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val)
 {
     switch(param)
     {
-    case AL_VOCAL_MORPHER_PHONEMEA:
-        *val = EnumFromPhenome(props->Vmorpher.PhonemeA);
-        break;
-
-    case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
-        *val = props->Vmorpher.PhonemeACoarseTuning;
-        break;
-
-    case AL_VOCAL_MORPHER_PHONEMEB:
-        *val = EnumFromPhenome(props->Vmorpher.PhonemeB);
-        break;
-
-    case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
-        *val = props->Vmorpher.PhonemeBCoarseTuning;
-        break;
-
-    case AL_VOCAL_MORPHER_WAVEFORM:
-        *val = EnumFromWaveform(props->Vmorpher.Waveform);
-        break;
+    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};
     }
 }
-void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
         param};
 }
-void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_VOCAL_MORPHER_RATE:
-        *val = props->Vmorpher.Rate;
+        *val = props.Rate;
         break;
 
     default:
@@ -236,551 +237,221 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
             param};
     }
 }
-void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Vmorpher_getParamf(props, param, vals); }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Vmorpher.Rate                 = AL_VOCAL_MORPHER_DEFAULT_RATE;
-    props.Vmorpher.PhonemeA             = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA);
-    props.Vmorpher.PhonemeB             = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB);
-    props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
-    props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
-    props.Vmorpher.Waveform             = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM);
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Vmorpher);
+void EffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-const EffectProps VmorpherEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
 
-using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxVocalMorpherEffectDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1;
-    EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1;
-    EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1;
-    EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1;
-    EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1;
-    EaxVocalMorpherEffectDirtyFlagsValue flRate : 1;
-}; // EaxPitchShifterEffectDirtyFlags
+using VocalMorpherCommitter = EaxCommitter<EaxVocalMorpherCommitter>;
 
-
-class EaxVocalMorpherEffect final :
-    public EaxEffect
-{
-public:
-    EaxVocalMorpherEffect();
-
-    void dispatch(const EaxEaxCall& eax_call) override;
-
-    // [[nodiscard]]
-    bool apply_deferred() override;
-
-private:
-    EAXVOCALMORPHERPROPERTIES eax_{};
-    EAXVOCALMORPHERPROPERTIES eax_d_{};
-    EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{};
-
-    void set_eax_defaults();
-
-    void set_efx_phoneme_a();
-    void set_efx_phoneme_a_coarse_tuning();
-    void set_efx_phoneme_b();
-    void set_efx_phoneme_b_coarse_tuning();
-    void set_efx_waveform();
-    void set_efx_rate();
-    void set_efx_defaults();
-
-    void get(const EaxEaxCall& eax_call);
-
-    void validate_phoneme_a(unsigned long ulPhonemeA);
-    void validate_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning);
-    void validate_phoneme_b(unsigned long ulPhonemeB);
-    void validate_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning);
-    void validate_waveform(unsigned long ulWaveform);
-    void validate_rate(float flRate);
-    void validate_all(const EAXVOCALMORPHERPROPERTIES& all);
-
-    void defer_phoneme_a(unsigned long ulPhonemeA);
-    void defer_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning);
-    void defer_phoneme_b(unsigned long ulPhonemeB);
-    void defer_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning);
-    void defer_waveform(unsigned long ulWaveform);
-    void defer_rate(float flRate);
-    void defer_all(const EAXVOCALMORPHERPROPERTIES& all);
-
-    void defer_phoneme_a(const EaxEaxCall& eax_call);
-    void defer_phoneme_a_coarse_tuning(const EaxEaxCall& eax_call);
-    void defer_phoneme_b(const EaxEaxCall& eax_call);
-    void defer_phoneme_b_coarse_tuning(const EaxEaxCall& eax_call);
-    void defer_waveform(const EaxEaxCall& eax_call);
-    void defer_rate(const EaxEaxCall& eax_call);
-    void defer_all(const EaxEaxCall& eax_call);
-
-    void set(const EaxEaxCall& eax_call);
-}; // EaxVocalMorpherEffect
-
-
-class EaxVocalMorpherEffectException :
-    public EaxException
-{
-public:
-    explicit EaxVocalMorpherEffectException(
-        const char* message)
-        :
-        EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
+struct PhonemeAValidator {
+    void operator()(unsigned long ulPhonemeA) const
     {
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Phoneme A",
+            ulPhonemeA,
+            EAXVOCALMORPHER_MINPHONEMEA,
+            EAXVOCALMORPHER_MAXPHONEMEA);
     }
-}; // EaxVocalMorpherEffectException
-
-
-EaxVocalMorpherEffect::EaxVocalMorpherEffect()
-    : EaxEffect{AL_EFFECT_VOCAL_MORPHER}
-{
-    set_eax_defaults();
-    set_efx_defaults();
-}
-
-void EaxVocalMorpherEffect::dispatch(const EaxEaxCall& eax_call)
-{
-    eax_call.is_get() ? get(eax_call) : set(eax_call);
-}
-
-void EaxVocalMorpherEffect::set_eax_defaults()
-{
-    eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
-    eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
-    eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
-    eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
-    eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
-    eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE;
-
-    eax_d_ = eax_;
-}
-
-void EaxVocalMorpherEffect::set_efx_phoneme_a()
-{
-    const auto phoneme_a = clamp(
-        static_cast<ALint>(eax_.ulPhonemeA),
-        AL_VOCAL_MORPHER_MIN_PHONEMEA,
-        AL_VOCAL_MORPHER_MAX_PHONEMEA);
-
-    const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a);
-    assert(efx_phoneme_a.has_value());
-    al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a;
-}
-
-void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning()
-{
-    const auto phoneme_a_coarse_tuning = clamp(
-        static_cast<ALint>(eax_.lPhonemeACoarseTuning),
-        AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING,
-        AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING);
-
-    al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning;
-}
-
-void EaxVocalMorpherEffect::set_efx_phoneme_b()
-{
-    const auto phoneme_b = clamp(
-        static_cast<ALint>(eax_.ulPhonemeB),
-        AL_VOCAL_MORPHER_MIN_PHONEMEB,
-        AL_VOCAL_MORPHER_MAX_PHONEMEB);
-
-    const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b);
-    assert(efx_phoneme_b.has_value());
-    al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b;
-}
-
-void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning()
-{
-    const auto phoneme_b_coarse_tuning = clamp(
-        static_cast<ALint>(eax_.lPhonemeBCoarseTuning),
-        AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING,
-        AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING);
-
-    al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning;
-}
-
-void EaxVocalMorpherEffect::set_efx_waveform()
-{
-    const auto waveform = clamp(
-        static_cast<ALint>(eax_.ulWaveform),
-        AL_VOCAL_MORPHER_MIN_WAVEFORM,
-        AL_VOCAL_MORPHER_MAX_WAVEFORM);
-
-    const auto wfx_waveform = WaveformFromEmum(waveform);
-    assert(wfx_waveform.has_value());
-    al_effect_props_.Vmorpher.Waveform = *wfx_waveform;
-}
-
-void EaxVocalMorpherEffect::set_efx_rate()
-{
-    const auto rate = clamp(
-        eax_.flRate,
-        AL_VOCAL_MORPHER_MIN_RATE,
-        AL_VOCAL_MORPHER_MAX_RATE);
-
-    al_effect_props_.Vmorpher.Rate = rate;
-}
+}; // PhonemeAValidator
 
-void EaxVocalMorpherEffect::set_efx_defaults()
-{
-    set_efx_phoneme_a();
-    set_efx_phoneme_a_coarse_tuning();
-    set_efx_phoneme_b();
-    set_efx_phoneme_b_coarse_tuning();
-    set_efx_waveform();
-    set_efx_rate();
-}
-
-void EaxVocalMorpherEffect::get(const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_property_id())
+struct PhonemeACoarseTuningValidator {
+    void operator()(long lPhonemeACoarseTuning) const
     {
-        case EAXVOCALMORPHER_NONE:
-            break;
-
-        case EAXVOCALMORPHER_ALLPARAMETERS:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEA:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeA);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeACoarseTuning);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEB:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeB);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeBCoarseTuning);
-            break;
-
-        case EAXVOCALMORPHER_WAVEFORM:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulWaveform);
-            break;
-
-        case EAXVOCALMORPHER_RATE:
-            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.flRate);
-            break;
-
-        default:
-            throw EaxVocalMorpherEffectException{"Unsupported property id."};
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Phoneme A Coarse Tuning",
+            lPhonemeACoarseTuning,
+            EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
+            EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
     }
-}
-
-void EaxVocalMorpherEffect::validate_phoneme_a(
-    unsigned long ulPhonemeA)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Phoneme A",
-        ulPhonemeA,
-        EAXVOCALMORPHER_MINPHONEMEA,
-        EAXVOCALMORPHER_MAXPHONEMEA);
-}
+}; // PhonemeACoarseTuningValidator
 
-void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning(
-    long lPhonemeACoarseTuning)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Phoneme A Coarse Tuning",
-        lPhonemeACoarseTuning,
-        EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
-        EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
-}
-
-void EaxVocalMorpherEffect::validate_phoneme_b(
-    unsigned long ulPhonemeB)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Phoneme B",
-        ulPhonemeB,
-        EAXVOCALMORPHER_MINPHONEMEB,
-        EAXVOCALMORPHER_MAXPHONEMEB);
-}
-
-void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning(
-    long lPhonemeBCoarseTuning)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Phoneme B Coarse Tuning",
-        lPhonemeBCoarseTuning,
-        EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
-        EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
-}
-
-void EaxVocalMorpherEffect::validate_waveform(
-    unsigned long ulWaveform)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Waveform",
-        ulWaveform,
-        EAXVOCALMORPHER_MINWAVEFORM,
-        EAXVOCALMORPHER_MAXWAVEFORM);
-}
-
-void EaxVocalMorpherEffect::validate_rate(
-    float flRate)
-{
-    eax_validate_range<EaxVocalMorpherEffectException>(
-        "Rate",
-        flRate,
-        EAXVOCALMORPHER_MINRATE,
-        EAXVOCALMORPHER_MAXRATE);
-}
-
-void EaxVocalMorpherEffect::validate_all(
-    const EAXVOCALMORPHERPROPERTIES& all)
-{
-    validate_phoneme_a(all.ulPhonemeA);
-    validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
-    validate_phoneme_b(all.ulPhonemeB);
-    validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
-    validate_waveform(all.ulWaveform);
-    validate_rate(all.flRate);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_a(
-    unsigned long ulPhonemeA)
-{
-    eax_d_.ulPhonemeA = ulPhonemeA;
-    eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
-    long lPhonemeACoarseTuning)
-{
-    eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning;
-    eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_b(
-    unsigned long ulPhonemeB)
-{
-    eax_d_.ulPhonemeB = ulPhonemeB;
-    eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
-    long lPhonemeBCoarseTuning)
-{
-    eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning;
-    eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning);
-}
-
-void EaxVocalMorpherEffect::defer_waveform(
-    unsigned long ulWaveform)
-{
-    eax_d_.ulWaveform = ulWaveform;
-    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
-}
-
-void EaxVocalMorpherEffect::defer_rate(
-    float flRate)
-{
-    eax_d_.flRate = flRate;
-    eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
-}
-
-void EaxVocalMorpherEffect::defer_all(
-    const EAXVOCALMORPHERPROPERTIES& all)
-{
-    defer_phoneme_a(all.ulPhonemeA);
-    defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
-    defer_phoneme_b(all.ulPhonemeB);
-    defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
-    defer_waveform(all.ulWaveform);
-    defer_rate(all.flRate);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_a(
-    const EaxEaxCall& eax_call)
-{
-    const auto& phoneme_a = eax_call.get_value<EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeA)>();
-
-    validate_phoneme_a(phoneme_a);
-    defer_phoneme_a(phoneme_a);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
-    const EaxEaxCall& eax_call)
-{
-    const auto& phoneme_a_coarse_tuning = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning)
-    >();
-
-    validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
-    defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_b(
-    const EaxEaxCall& eax_call)
-{
-    const auto& phoneme_b = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB)
-    >();
-
-    validate_phoneme_b(phoneme_b);
-    defer_phoneme_b(phoneme_b);
-}
-
-void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
-    const EaxEaxCall& eax_call)
-{
-    const auto& phoneme_b_coarse_tuning = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning)
-    >();
-
-    validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
-    defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
-}
-
-void EaxVocalMorpherEffect::defer_waveform(
-    const EaxEaxCall& eax_call)
-{
-    const auto& waveform = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform)
-    >();
-
-    validate_waveform(waveform);
-    defer_waveform(waveform);
-}
-
-void EaxVocalMorpherEffect::defer_rate(
-    const EaxEaxCall& eax_call)
-{
-    const auto& rate = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const decltype(EAXVOCALMORPHERPROPERTIES::flRate)
-    >();
-
-    validate_rate(rate);
-    defer_rate(rate);
-}
-
-void EaxVocalMorpherEffect::defer_all(
-    const EaxEaxCall& eax_call)
-{
-    const auto& all = eax_call.get_value<
-        EaxVocalMorpherEffectException,
-        const EAXVOCALMORPHERPROPERTIES
-    >();
-
-    validate_all(all);
-    defer_all(all);
-}
-
-// [[nodiscard]]
-bool EaxVocalMorpherEffect::apply_deferred()
-{
-    if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{})
+struct PhonemeBValidator {
+    void operator()(unsigned long ulPhonemeB) const
     {
-        return false;
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Phoneme B",
+            ulPhonemeB,
+            EAXVOCALMORPHER_MINPHONEMEB,
+            EAXVOCALMORPHER_MAXPHONEMEB);
     }
+}; // PhonemeBValidator
 
-    eax_ = eax_d_;
-
-    if (eax_dirty_flags_.ulPhonemeA)
+struct PhonemeBCoarseTuningValidator {
+    void operator()(long lPhonemeBCoarseTuning) const
     {
-        set_efx_phoneme_a();
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Phoneme B Coarse Tuning",
+            lPhonemeBCoarseTuning,
+            EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
+            EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
     }
+}; // PhonemeBCoarseTuningValidator
 
-    if (eax_dirty_flags_.lPhonemeACoarseTuning)
+struct WaveformValidator {
+    void operator()(unsigned long ulWaveform) const
     {
-        set_efx_phoneme_a_coarse_tuning();
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Waveform",
+            ulWaveform,
+            EAXVOCALMORPHER_MINWAVEFORM,
+            EAXVOCALMORPHER_MAXWAVEFORM);
     }
+}; // WaveformValidator
 
-    if (eax_dirty_flags_.ulPhonemeB)
+struct RateValidator {
+    void operator()(float flRate) const
     {
-        set_efx_phoneme_b();
+        eax_validate_range<VocalMorpherCommitter::Exception>(
+            "Rate",
+            flRate,
+            EAXVOCALMORPHER_MINRATE,
+            EAXVOCALMORPHER_MAXRATE);
     }
+}; // RateValidator
 
-    if (eax_dirty_flags_.lPhonemeBCoarseTuning)
+struct AllValidator {
+    void operator()(const EAXVOCALMORPHERPROPERTIES& all) const
     {
-        set_efx_phoneme_b_coarse_tuning();
+        PhonemeAValidator{}(all.ulPhonemeA);
+        PhonemeACoarseTuningValidator{}(all.lPhonemeACoarseTuning);
+        PhonemeBValidator{}(all.ulPhonemeB);
+        PhonemeBCoarseTuningValidator{}(all.lPhonemeBCoarseTuning);
+        WaveformValidator{}(all.ulWaveform);
+        RateValidator{}(all.flRate);
     }
+}; // AllValidator
 
-    if (eax_dirty_flags_.ulWaveform)
-    {
-        set_efx_waveform();
-    }
-
-    if (eax_dirty_flags_.flRate)
-    {
-        set_efx_rate();
-    }
+} // namespace
 
-    eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{};
+template<>
+struct VocalMorpherCommitter::Exception : public EaxException {
+    explicit Exception(const char *message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
+    { }
+};
 
-    return true;
+template<>
+[[noreturn]] void VocalMorpherCommitter::fail(const char *message)
+{
+    throw Exception{message};
 }
 
-void EaxVocalMorpherEffect::set(const EaxEaxCall& eax_call)
+bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props)
 {
-    switch(eax_call.get_property_id())
-    {
-        case EAXVOCALMORPHER_NONE:
-            break;
-
-        case EAXVOCALMORPHER_ALLPARAMETERS:
-            defer_all(eax_call);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEA:
-            defer_phoneme_a(eax_call);
-            break;
-
-        case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
-            defer_phoneme_a_coarse_tuning(eax_call);
-            break;
+    if(auto *cur = std::get_if<EAXVOCALMORPHERPROPERTIES>(&mEaxProps); cur && *cur == props)
+        return false;
 
-        case EAXVOCALMORPHER_PHONEMEB:
-            defer_phoneme_b(eax_call);
-            break;
+    mEaxProps = props;
 
-        case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
-            defer_phoneme_b_coarse_tuning(eax_call);
-            break;
+    auto get_phoneme = [](unsigned long phoneme) noexcept
+    {
+#define HANDLE_PHENOME(x) case x: return VMorpherPhenome::x
+        switch(phoneme)
+        {
+        HANDLE_PHENOME(A);
+        HANDLE_PHENOME(E);
+        HANDLE_PHENOME(I);
+        HANDLE_PHENOME(O);
+        HANDLE_PHENOME(U);
+        HANDLE_PHENOME(AA);
+        HANDLE_PHENOME(AE);
+        HANDLE_PHENOME(AH);
+        HANDLE_PHENOME(AO);
+        HANDLE_PHENOME(EH);
+        HANDLE_PHENOME(ER);
+        HANDLE_PHENOME(IH);
+        HANDLE_PHENOME(IY);
+        HANDLE_PHENOME(UH);
+        HANDLE_PHENOME(UW);
+        HANDLE_PHENOME(B);
+        HANDLE_PHENOME(D);
+        HANDLE_PHENOME(F);
+        HANDLE_PHENOME(G);
+        HANDLE_PHENOME(J);
+        HANDLE_PHENOME(K);
+        HANDLE_PHENOME(L);
+        HANDLE_PHENOME(M);
+        HANDLE_PHENOME(N);
+        HANDLE_PHENOME(P);
+        HANDLE_PHENOME(R);
+        HANDLE_PHENOME(S);
+        HANDLE_PHENOME(T);
+        HANDLE_PHENOME(V);
+        HANDLE_PHENOME(Z);
+        }
+        return VMorpherPhenome::A;
+#undef HANDLE_PHENOME
+    };
+    auto get_waveform = [](unsigned long form) noexcept
+    {
+        if(form == EAX_VOCALMORPHER_SINUSOID) return VMorpherWaveform::Sinusoid;
+        if(form == EAX_VOCALMORPHER_TRIANGLE) return VMorpherWaveform::Triangle;
+        if(form == EAX_VOCALMORPHER_SAWTOOTH) return VMorpherWaveform::Sawtooth;
+        return VMorpherWaveform::Sinusoid;
+    };
+
+    mAlProps = [&]{
+        VmorpherProps ret{};
+        ret.PhonemeA = get_phoneme(props.ulPhonemeA);
+        ret.PhonemeACoarseTuning = static_cast<int>(props.lPhonemeACoarseTuning);
+        ret.PhonemeB = get_phoneme(props.ulPhonemeB);
+        ret.PhonemeBCoarseTuning = static_cast<int>(props.lPhonemeBCoarseTuning);
+        ret.Waveform = get_waveform(props.ulWaveform);
+        ret.Rate = props.flRate;
+        return ret;
+    }();
 
-        case EAXVOCALMORPHER_WAVEFORM:
-            defer_waveform(eax_call);
-            break;
+    return true;
+}
 
-        case EAXVOCALMORPHER_RATE:
-            defer_rate(eax_call);
-            break;
+void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
+{
+    static constexpr EAXVOCALMORPHERPROPERTIES defprops{[]
+    {
+        EAXVOCALMORPHERPROPERTIES ret{};
+        ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
+        ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
+        ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
+        ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
+        ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
+        ret.flRate = EAXVOCALMORPHER_DEFAULTRATE;
+        return ret;
+    }()};
+    props = defprops;
+}
 
-        default:
-            throw EaxVocalMorpherEffectException{"Unsupported property id."};
+void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props)
+{
+    switch(call.get_property_id())
+    {
+    case EAXVOCALMORPHER_NONE: break;
+    case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value<Exception>(props); break;
+    case EAXVOCALMORPHER_PHONEMEA: call.set_value<Exception>(props.ulPhonemeA); break;
+    case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value<Exception>(props.lPhonemeACoarseTuning); break;
+    case EAXVOCALMORPHER_PHONEMEB: call.set_value<Exception>(props.ulPhonemeB); break;
+    case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value<Exception>(props.lPhonemeBCoarseTuning); break;
+    case EAXVOCALMORPHER_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
+    case EAXVOCALMORPHER_RATE: call.set_value<Exception>(props.flRate); break;
+    default: fail_unknown_property_id();
     }
 }
 
-} // namespace
-
-
-EaxEffectUPtr eax_create_eax_vocal_morpher_effect()
+void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props)
 {
-    return std::make_unique<EaxVocalMorpherEffect>();
+    switch(call.get_property_id())
+    {
+    case EAXVOCALMORPHER_NONE: break;
+    case EAXVOCALMORPHER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
+    case EAXVOCALMORPHER_PHONEMEA: defer<PhonemeAValidator>(call, props.ulPhonemeA); break;
+    case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer<PhonemeACoarseTuningValidator>(call, props.lPhonemeACoarseTuning); break;
+    case EAXVOCALMORPHER_PHONEMEB: defer<PhonemeBValidator>(call, props.ulPhonemeB); break;
+    case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer<PhonemeBCoarseTuningValidator>(call, props.lPhonemeBCoarseTuning); break;
+    case EAXVOCALMORPHER_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
+    case EAXVOCALMORPHER_RATE: defer<RateValidator>(call, props.flRate); break;
+    default: fail_unknown_property_id();
+    }
 }
 
 #endif // ALSOFT_EAX

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

@@ -20,6 +20,8 @@
 
 #include "config.h"
 
+#include "error.h"
+
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
@@ -29,27 +31,45 @@
 #include <csignal>
 #include <cstdarg>
 #include <cstdio>
+#include <cstdlib>
 #include <cstring>
-#include <mutex>
+#include <limits>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
 
 #include "AL/al.h"
 #include "AL/alc.h"
 
+#include "al/debug.h"
+#include "alc/alconfig.h"
 #include "alc/context.h"
-#include "almalloc.h"
-#include "core/except.h"
+#include "alc/inprogext.h"
 #include "core/logging.h"
 #include "opthelpers.h"
-#include "vector.h"
+#include "strutils.h"
 
 
-bool TrapALError{false};
+namespace al {
+context_error::context_error(ALenum 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) */
+}
+context_error::~context_error() = default;
+} /* namespace al */
 
 void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
 {
-    auto message = al::vector<char>(256);
+    auto message = std::vector<char>(256);
 
-    va_list args, args2;
+    /* 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)};
@@ -60,9 +80,15 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
     }
     va_end(args2);
     va_end(args);
+    /* NOLINTEND(*-array-to-pointer-decay) */
 
-    if(msglen >= 0) msg = message.data();
-    else msg = "<internal error constructing message>";
+    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);
@@ -77,30 +103,55 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
 #endif
     }
 
-    ALenum curerr{AL_NO_ERROR};
-    mLastError.compare_exchange_strong(curerr, errorCode);
+    if(mLastThreadError.get() == AL_NO_ERROR)
+        mLastThreadError.set(errorCode);
+
+    debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High,
+        {msg, static_cast<uint>(msglen)});
 }
 
-AL_API ALenum AL_APIENTRY alGetError(void)
-START_API_FUNC
+/* Special-case alGetError since it (potentially) raises a debug signal and
+ * returns a non-default value for a null context.
+ */
+AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum
 {
-    ContextRef context{GetContextRef()};
-    if(unlikely(!context))
+    if(auto context = GetContextRef()) LIKELY
+        return alGetErrorDirect(context.get());
+
+    auto get_value = [](const char *envname, const char *optname) -> ALenum
     {
-        static constexpr ALenum deferror{AL_INVALID_OPERATION};
-        WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
-        if(TrapALError)
+        auto optstr = al::getenv(envname);
+        if(!optstr)
+            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());
+        }
+        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);
+    if(TrapALError)
+    {
 #ifdef _WIN32
-            if(IsDebuggerPresent())
-                DebugBreak();
+        if(IsDebuggerPresent())
+            DebugBreak();
 #elif defined(SIGTRAP)
-            raise(SIGTRAP);
+        raise(SIGTRAP);
 #endif
-        }
-        return deferror;
     }
+    return deferror;
+}
 
-    return context->mLastError.exchange(AL_NO_ERROR);
+FORCE_ALIGN ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept
+{
+    ALenum ret{context->mLastThreadError.get()};
+    if(ret != AL_NO_ERROR) UNLIKELY
+        context->mLastThreadError.set(AL_NO_ERROR);
+    return ret;
 }
-END_API_FUNC

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

@@ -0,0 +1,27 @@
+#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 */

+ 121 - 98
libs/openal-soft/al/event.cpp

@@ -3,39 +3,54 @@
 
 #include "event.h"
 
-#include <algorithm>
+#include <array>
 #include <atomic>
-#include <cstring>
+#include <bitset>
 #include <exception>
 #include <memory>
 #include <mutex>
 #include <new>
+#include <optional>
 #include <string>
+#include <string_view>
 #include <thread>
+#include <tuple>
 #include <utility>
+#include <variant>
 
 #include "AL/al.h"
 #include "AL/alc.h"
+#include "AL/alext.h"
 
-#include "albyte.h"
 #include "alc/context.h"
-#include "alc/effects/base.h"
 #include "alc/inprogext.h"
-#include "almalloc.h"
+#include "alsem.h"
+#include "alspan.h"
 #include "core/async_event.h"
-#include "core/except.h"
+#include "core/context.h"
+#include "core/effects/base.h"
 #include "core/logging.h"
-#include "core/voice_change.h"
+#include "debug.h"
+#include "direct_defs.h"
+#include "error.h"
+#include "intrusive_ptr.h"
 #include "opthelpers.h"
 #include "ringbuffer.h"
-#include "threads.h"
 
 
-static int EventThread(ALCcontext *context)
+namespace {
+
+template<typename... Ts>
+struct overloaded : Ts... { using Ts::operator()...; };
+
+template<typename... Ts>
+overloaded(Ts...) -> overloaded<Ts...>;
+
+int EventThread(ALCcontext *context)
 {
     RingBuffer *ring{context->mAsyncEvents.get()};
     bool quitnow{false};
-    while(likely(!quitnow))
+    while(!quitnow)
     {
         auto evt_data = ring->getReadVector().first;
         if(evt_data.len == 0)
@@ -44,81 +59,100 @@ static int EventThread(ALCcontext *context)
             continue;
         }
 
-        std::lock_guard<std::mutex> _{context->mEventCbLock};
-        do {
-            auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
-            evt_data.buf += sizeof(AsyncEvent);
-            evt_data.len -= 1;
-
-            AsyncEvent evt{*evt_ptr};
-            al::destroy_at(evt_ptr);
-            ring->readAdvance(1);
-
-            quitnow = evt.EnumType == AsyncEvent::KillThread;
-            if(unlikely(quitnow)) break;
+        std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
+        auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)),
+            evt_data.len};
+        for(auto &event : evt_span)
+        {
+            quitnow = std::holds_alternative<AsyncKillThread>(event);
+            if(quitnow) UNLIKELY break;
 
-            if(evt.EnumType == AsyncEvent::ReleaseEffectState)
+            auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
+            auto proc_killthread = [](AsyncKillThread&) { };
+            auto proc_release = [](AsyncEffectReleaseEvent &evt)
             {
-                evt.u.mEffectState->release();
-                continue;
-            }
-
-            uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
-            if(!context->mEventCb) continue;
-
-            if(evt.EnumType == AsyncEvent::SourceStateChange)
+                al::intrusive_ptr<EffectState>{evt.mEffectState};
+            };
+            auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt)
             {
-                if(!(enabledevts&AsyncEvent::SourceStateChange))
-                    continue;
+                if(!context->mEventCb
+                    || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState)))
+                    return;
+
                 ALuint state{};
-                std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
+                std::string msg{"Source ID " + std::to_string(evt.mId)};
                 msg += " state has changed to ";
-                switch(evt.u.srcstate.state)
+                switch(evt.mState)
                 {
-                case AsyncEvent::SrcState::Reset:
+                case AsyncSrcState::Reset:
                     msg += "AL_INITIAL";
                     state = AL_INITIAL;
                     break;
-                case AsyncEvent::SrcState::Stop:
+                case AsyncSrcState::Stop:
                     msg += "AL_STOPPED";
                     state = AL_STOPPED;
                     break;
-                case AsyncEvent::SrcState::Play:
+                case AsyncSrcState::Play:
                     msg += "AL_PLAYING";
                     state = AL_PLAYING;
                     break;
-                case AsyncEvent::SrcState::Pause:
+                case AsyncSrcState::Pause:
                     msg += "AL_PAUSED";
                     state = AL_PAUSED;
                     break;
                 }
-                context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
-                    state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
-            }
-            else if(evt.EnumType == AsyncEvent::BufferCompleted)
+                context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state,
+                    static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
+            };
+            auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt)
             {
-                if(!(enabledevts&AsyncEvent::BufferCompleted))
-                    continue;
-                std::string msg{std::to_string(evt.u.bufcomp.count)};
-                if(evt.u.bufcomp.count == 1) msg += " buffer completed";
+                if(!context->mEventCb
+                    || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted)))
+                    return;
+
+                std::string msg{std::to_string(evt.mCount)};
+                if(evt.mCount == 1) msg += " buffer completed";
                 else msg += " buffers completed";
-                context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
-                    evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
-                    context->mEventParam);
-            }
-            else if(evt.EnumType == AsyncEvent::Disconnected)
+                context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount,
+                    static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
+            };
+            auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt)
             {
-                if(!(enabledevts&AsyncEvent::Disconnected))
-                    continue;
-                context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
-                    static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg,
-                    context->mEventParam);
-            }
-        } while(evt_data.len != 0);
+                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)))
+                    context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
+                        static_cast<ALsizei>(message.length()), message.data(),
+                        context->mEventParam);
+            };
+
+            std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect,
+                proc_killthread}, event);
+        }
+        std::destroy(evt_span.begin(), evt_span.end());
+        ring->readAdvance(evt_span.size());
     }
     return 0;
 }
 
+constexpr std::optional<AsyncEnableBits> GetEventType(ALenum etype) noexcept
+{
+    switch(etype)
+    {
+    case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted;
+    case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected;
+    case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState;
+    }
+    return std::nullopt;
+}
+
+} // namespace
+
+
 void StartEventThrd(ALCcontext *ctx)
 {
     try {
@@ -143,7 +177,7 @@ void StopEventThrd(ALCcontext *ctx)
             evt_data = ring->getWriteVector().first;
         } while(evt_data.len == 0);
     }
-    al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), AsyncEvent::KillThread);
+    std::ignore = InitAsyncEvent<AsyncKillThread>(evt_data.buf);
     ring->writeAdvance(1);
 
     ctx->mEventSem.post();
@@ -151,38 +185,29 @@ void StopEventThrd(ALCcontext *ctx)
         ctx->mEventThread.join();
 }
 
-AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if(unlikely(!context)) return;
+AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable)
+FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count,
+    const ALenum *types, ALboolean enable) noexcept
+try {
+    if(count < 0)
+        throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count};
+    if(count <= 0) UNLIKELY return;
 
-    if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
-    if(count <= 0) return;
-    if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
+    if(!types)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
 
-    uint flags{0};
-    const ALenum *types_end = types+count;
-    auto bad_type = std::find_if_not(types, types_end,
-        [&flags](ALenum type) noexcept -> bool
-        {
-            if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
-                flags |= AsyncEvent::BufferCompleted;
-            else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
-                flags |= AsyncEvent::SourceStateChange;
-            else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
-                flags |= AsyncEvent::Disconnected;
-            else
-                return false;
-            return true;
-        }
-    );
-    if(bad_type != types_end)
-        SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type);
+    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};
+        flags.set(al::to_underlying(*etype));
+    }
 
     if(enable)
     {
-        uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
+        auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed);
         while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags,
             std::memory_order_acq_rel, std::memory_order_acquire) == 0)
         {
@@ -193,7 +218,7 @@ START_API_FUNC
     }
     else
     {
-        uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
+        auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed);
         while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags,
             std::memory_order_acq_rel, std::memory_order_acquire) == 0)
         {
@@ -201,20 +226,18 @@ START_API_FUNC
         /* Wait to ensure the event handler sees the changed flags before
          * returning.
          */
-        std::lock_guard<std::mutex> _{context->mEventCbLock};
+        std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
     }
 }
-END_API_FUNC
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
 
-AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
-START_API_FUNC
+AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam)
+FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context,
+    ALEVENTPROCSOFT callback, void *userParam) noexcept
 {
-    ContextRef context{GetContextRef()};
-    if(unlikely(!context)) return;
-
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    std::lock_guard<std::mutex> __{context->mEventCbLock};
+    std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
     context->mEventCb = callback;
     context->mEventParam = userParam;
 }
-END_API_FUNC

+ 29 - 30
libs/openal-soft/al/extension.cpp

@@ -20,60 +20,59 @@
 
 #include "config.h"
 
-#include <cctype>
-#include <cstdlib>
-#include <cstring>
+#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 "core/except.h"
+#include "direct_defs.h"
 #include "opthelpers.h"
 
 
-AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
-START_API_FUNC
+AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName)
+FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept
 {
-    ContextRef context{GetContextRef()};
-    if(unlikely(!context)) return AL_FALSE;
-
-    if(!extName)
-        SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
+    if(!extName) UNLIKELY
+    {
+        context->setError(AL_INVALID_VALUE, "NULL pointer");
+        return AL_FALSE;
+    }
 
-    size_t len{strlen(extName)};
-    const char *ptr{context->mExtensionList};
-    while(ptr && *ptr)
+    const std::string_view tofind{extName};
+    for(std::string_view ext : context->mExtensions)
     {
-        if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
+        if(al::case_compare(ext, tofind) == 0)
             return AL_TRUE;
-
-        if((ptr=strchr(ptr, ' ')) != nullptr)
-        {
-            do {
-                ++ptr;
-            } while(isspace(*ptr));
-        }
     }
 
     return AL_FALSE;
 }
-END_API_FUNC
 
 
-AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
-START_API_FUNC
+AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept
 {
     if(!funcName) return nullptr;
     return alcGetProcAddress(nullptr, funcName);
 }
-END_API_FUNC
 
-AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
-START_API_FUNC
+FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept
+{
+    if(!funcName) return nullptr;
+    return alcGetProcAddress(nullptr, funcName);
+}
+
+AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept
+{
+    if(!enumName) return ALenum{0};
+    return alcGetEnumValue(nullptr, enumName);
+}
+
+FORCE_ALIGN ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept
 {
-    if(!enumName) return static_cast<ALenum>(0);
+    if(!enumName) return ALenum{0};
     return alcGetEnumValue(nullptr, enumName);
 }
-END_API_FUNC

+ 400 - 487
libs/openal-soft/al/filter.cpp

@@ -29,8 +29,10 @@
 #include <iterator>
 #include <memory>
 #include <mutex>
-#include <new>
 #include <numeric>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 #include "AL/al.h"
 #include "AL/alc.h"
@@ -39,247 +41,19 @@
 #include "albit.h"
 #include "alc/context.h"
 #include "alc/device.h"
+#include "alc/inprogext.h"
 #include "almalloc.h"
 #include "alnumeric.h"
-#include "core/except.h"
+#include "alspan.h"
+#include "direct_defs.h"
+#include "error.h"
+#include "intrusive_ptr.h"
 #include "opthelpers.h"
-#include "vector.h"
 
 
 namespace {
 
-class filter_exception final : public al::base_exception {
-    ALenum mErrorCode;
-
-public:
-#ifdef __USE_MINGW_ANSI_STDIO
-    [[gnu::format(gnu_printf, 3, 4)]]
-#else
-    [[gnu::format(printf, 3, 4)]]
-#endif
-    filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
-    {
-        std::va_list args;
-        va_start(args, msg);
-        setMessage(msg, args);
-        va_end(args);
-    }
-    ALenum errorCode() const noexcept { return mErrorCode; }
-};
-
-
-#define DEFINE_ALFILTER_VTABLE(T)                                  \
-const ALfilter::Vtable T##_vtable = {                              \
-    T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv,  \
-    T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv,  \
-}
-
-void ALlowpass_setParami(ALfilter*, ALenum param, int)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
-void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x",
-        param};
-}
-void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val)
-{
-    switch(param)
-    {
-    case AL_LOWPASS_GAIN:
-        if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN))
-            throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val};
-        filter->Gain = val;
-        break;
-
-    case AL_LOWPASS_GAINHF:
-        if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
-            throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val};
-        filter->GainHF = val;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
-    }
-}
-void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ ALlowpass_setParamf(filter, param, vals[0]); }
-
-void ALlowpass_getParami(const ALfilter*, ALenum param, int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
-void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x",
-        param};
-}
-void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val)
-{
-    switch(param)
-    {
-    case AL_LOWPASS_GAIN:
-        *val = filter->Gain;
-        break;
-
-    case AL_LOWPASS_GAINHF:
-        *val = filter->GainHF;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
-    }
-}
-void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ ALlowpass_getParamf(filter, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALlowpass);
-
-
-void ALhighpass_setParami(ALfilter*, ALenum param, int)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
-void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x",
-        param};
-}
-void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val)
-{
-    switch(param)
-    {
-    case AL_HIGHPASS_GAIN:
-        if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN))
-            throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val};
-        filter->Gain = val;
-        break;
-
-    case AL_HIGHPASS_GAINLF:
-        if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
-            throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val};
-        filter->GainLF = val;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param};
-    }
-}
-void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ ALhighpass_setParamf(filter, param, vals[0]); }
-
-void ALhighpass_getParami(const ALfilter*, ALenum param, int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
-void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x",
-        param};
-}
-void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val)
-{
-    switch(param)
-    {
-    case AL_HIGHPASS_GAIN:
-        *val = filter->Gain;
-        break;
-
-    case AL_HIGHPASS_GAINLF:
-        *val = filter->GainLF;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param};
-    }
-}
-void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ ALhighpass_getParamf(filter, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALhighpass);
-
-
-void ALbandpass_setParami(ALfilter*, ALenum param, int)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
-void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x",
-        param};
-}
-void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val)
-{
-    switch(param)
-    {
-    case AL_BANDPASS_GAIN:
-        if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN))
-            throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val};
-        filter->Gain = val;
-        break;
-
-    case AL_BANDPASS_GAINHF:
-        if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
-            throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val};
-        filter->GainHF = val;
-        break;
-
-    case AL_BANDPASS_GAINLF:
-        if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
-            throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val};
-        filter->GainLF = val;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
-    }
-}
-void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
-{ ALbandpass_setParamf(filter, param, vals[0]); }
-
-void ALbandpass_getParami(const ALfilter*, ALenum param, int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
-void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*)
-{
-    throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x",
-        param};
-}
-void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *val)
-{
-    switch(param)
-    {
-    case AL_BANDPASS_GAIN:
-        *val = filter->Gain;
-        break;
-
-    case AL_BANDPASS_GAINHF:
-        *val = filter->GainHF;
-        break;
-
-    case AL_BANDPASS_GAINLF:
-        *val = filter->GainLF;
-        break;
-
-    default:
-        throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
-    }
-}
-void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
-{ ALbandpass_getParamf(filter, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALbandpass);
-
-
-void ALnullfilter_setParami(ALfilter*, ALenum param, int)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_setParamf(ALfilter*, ALenum param, float)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-
-void ALnullfilter_getParami(const ALfilter*, ALenum param, int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*)
-{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
-
-DEFINE_ALFILTER_VTABLE(ALnullfilter);
+using SubListAllocator = al::allocator<std::array<ALfilter,64>>;
 
 
 void InitFilterParams(ALfilter *filter, ALenum type)
@@ -288,68 +62,66 @@ void InitFilterParams(ALfilter *filter, ALenum type)
     {
         filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
         filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
-        filter->HFReference = LOWPASSFREQREF;
+        filter->HFReference = LowPassFreqRef;
         filter->GainLF = 1.0f;
-        filter->LFReference = HIGHPASSFREQREF;
-        filter->vtab = &ALlowpass_vtable;
+        filter->LFReference = HighPassFreqRef;
+        filter->mTypeVariant.emplace<LowpassFilterTable>();
     }
     else if(type == AL_FILTER_HIGHPASS)
     {
         filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
         filter->GainHF = 1.0f;
-        filter->HFReference = LOWPASSFREQREF;
+        filter->HFReference = LowPassFreqRef;
         filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
-        filter->LFReference = HIGHPASSFREQREF;
-        filter->vtab = &ALhighpass_vtable;
+        filter->LFReference = HighPassFreqRef;
+        filter->mTypeVariant.emplace<HighpassFilterTable>();
     }
     else if(type == AL_FILTER_BANDPASS)
     {
         filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
         filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
-        filter->HFReference = LOWPASSFREQREF;
+        filter->HFReference = LowPassFreqRef;
         filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
-        filter->LFReference = HIGHPASSFREQREF;
-        filter->vtab = &ALbandpass_vtable;
+        filter->LFReference = HighPassFreqRef;
+        filter->mTypeVariant.emplace<BandpassFilterTable>();
     }
     else
     {
         filter->Gain = 1.0f;
         filter->GainHF = 1.0f;
-        filter->HFReference = LOWPASSFREQREF;
+        filter->HFReference = LowPassFreqRef;
         filter->GainLF = 1.0f;
-        filter->LFReference = HIGHPASSFREQREF;
-        filter->vtab = &ALnullfilter_vtable;
+        filter->LFReference = HighPassFreqRef;
+        filter->mTypeVariant.emplace<NullFilterTable>();
     }
     filter->type = type;
 }
 
-bool EnsureFilters(ALCdevice *device, size_t needed)
-{
-    size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0},
+auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool
+try {
+    size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz,
         [](size_t cur, const FilterSubList &sublist) noexcept -> size_t
         { return cur + static_cast<ALuint>(al::popcount(sublist.FreeMask)); })};
 
     while(needed > count)
     {
-        if UNLIKELY(device->FilterList.size() >= 1<<25)
+        if(device->FilterList.size() >= 1<<25) UNLIKELY
             return false;
 
-        device->FilterList.emplace_back();
-        auto sublist = device->FilterList.end() - 1;
-        sublist->FreeMask = ~0_u64;
-        sublist->Filters = static_cast<ALfilter*>(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64));
-        if UNLIKELY(!sublist->Filters)
-        {
-            device->FilterList.pop_back();
-            return false;
-        }
-        count += 64;
+        FilterSubList sublist{};
+        sublist.FreeMask = ~0_u64;
+        sublist.Filters = SubListAllocator{}.allocate(1);
+        device->FilterList.emplace_back(std::move(sublist));
+        count += std::tuple_size_v<SubListAllocator::value_type>;
     }
     return true;
 }
+catch(...) {
+    return false;
+}
 
 
-ALfilter *AllocFilter(ALCdevice *device)
+ALfilter *AllocFilter(ALCdevice *device) noexcept
 {
     auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
         [](const FilterSubList &entry) noexcept -> bool
@@ -358,7 +130,7 @@ ALfilter *AllocFilter(ALCdevice *device)
     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
     ASSUME(slidx < 64);
 
-    ALfilter *filter{al::construct_at(sublist->Filters + slidx)};
+    ALfilter *filter{al::construct_at(al::to_address(sublist->Filters->begin() + slidx))};
     InitFilterParams(filter, AL_FILTER_NULL);
 
     /* Add 1 to avoid filter ID 0. */
@@ -371,347 +143,488 @@ ALfilter *AllocFilter(ALCdevice *device)
 
 void FreeFilter(ALCdevice *device, ALfilter *filter)
 {
+    device->mFilterNames.erase(filter->id);
+
     const ALuint id{filter->id - 1};
     const size_t lidx{id >> 6};
     const ALuint slidx{id & 0x3f};
 
-    al::destroy_at(filter);
+    std::destroy_at(filter);
 
     device->FilterList[lidx].FreeMask |= 1_u64 << slidx;
 }
 
 
-inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
+inline auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter*
 {
     const size_t lidx{(id-1) >> 6};
     const ALuint slidx{(id-1) & 0x3f};
 
-    if UNLIKELY(lidx >= device->FilterList.size())
+    if(lidx >= device->FilterList.size()) UNLIKELY
         return nullptr;
     FilterSubList &sublist = device->FilterList[lidx];
-    if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+    if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
         return nullptr;
-    return sublist.Filters + slidx;
+    return al::to_address(sublist.Filters->begin() + slidx);
 }
 
 } // namespace
 
-AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
-START_API_FUNC
+/* 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}; }
+template<>
+void FilterTable<NullFilterTable>::setParamiv(ALfilter*, ALenum param, const int*)
+{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
+template<>
+void FilterTable<NullFilterTable>::setParamf(ALfilter*, ALenum param, float)
+{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", 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}; }
+template<>
+void FilterTable<NullFilterTable>::getParami(const ALfilter*, ALenum param, int*)
+{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", 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}; }
+template<>
+void FilterTable<NullFilterTable>::getParamf(const ALfilter*, ALenum param, float*)
+{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", 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}; }
+
+/* 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}; }
+template<>
+void FilterTable<LowpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
+{ setParami(filter, param, *values); }
+template<>
+void FilterTable<LowpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val)
 {
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
-    if UNLIKELY(n < 0)
-        context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
-    if UNLIKELY(n <= 0) return;
-
-    ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->FilterLock};
-    if(!EnsureFilters(device, static_cast<ALuint>(n)))
+    switch(param)
     {
-        context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
+    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};
+        filter->Gain = val;
         return;
-    }
 
-    if LIKELY(n == 1)
+    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};
+        filter->GainHF = val;
+        return;
+    }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
+}
+template<>
+void FilterTable<LowpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(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}; }
+template<>
+void FilterTable<LowpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
+{ getParami(filter, param, values); }
+template<>
+void FilterTable<LowpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val)
+{
+    switch(param)
     {
-        /* Special handling for the easy and normal case. */
-        ALfilter *filter{AllocFilter(device)};
-        if(filter) filters[0] = filter->id;
+    case AL_LOWPASS_GAIN: *val = filter->Gain; return;
+    case AL_LOWPASS_GAINHF: *val = filter->GainHF; return;
     }
-    else
+    throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
+}
+template<>
+void FilterTable<LowpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
+{ getParamf(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}; }
+template<>
+void FilterTable<HighpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
+{ setParami(filter, param, *values); }
+template<>
+void FilterTable<HighpassFilterTable>::setParamf(ALfilter *filter, ALenum param, float val)
+{
+    switch(param)
     {
-        /* Store the allocated buffer IDs in a separate local list, to avoid
-         * modifying the user storage in case of failure.
-         */
-        al::vector<ALuint> ids;
-        ids.reserve(static_cast<ALuint>(n));
-        do {
-            ALfilter *filter{AllocFilter(device)};
-            ids.emplace_back(filter->id);
-        } while(--n);
-        std::copy(ids.begin(), ids.end(), filters);
+    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};
+        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};
+        filter->GainLF = val;
+        return;
     }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param};
 }
-END_API_FUNC
+template<>
+void FilterTable<HighpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(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}; }
+template<>
+void FilterTable<HighpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
+{ getParami(filter, param, values); }
+template<>
+void FilterTable<HighpassFilterTable>::getParamf(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};
+}
+template<>
+void FilterTable<HighpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
+{ getParamf(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}; }
+template<>
+void FilterTable<BandpassFilterTable>::setParamiv(ALfilter *filter, ALenum param, const int *values)
+{ setParami(filter, param, *values); }
+template<>
+void FilterTable<BandpassFilterTable>::setParamf(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};
+        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};
+        filter->GainHF = val;
+        return;
 
-AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
-START_API_FUNC
+    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};
+        filter->GainLF = val;
+        return;
+    }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
+}
+template<>
+void FilterTable<BandpassFilterTable>::setParamfv(ALfilter *filter, ALenum param, const float *vals)
+{ setParamf(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}; }
+template<>
+void FilterTable<BandpassFilterTable>::getParamiv(const ALfilter *filter, ALenum param, int *values)
+{ getParami(filter, param, values); }
+template<>
+void FilterTable<BandpassFilterTable>::getParamf(const ALfilter *filter, ALenum param, float *val)
 {
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+    switch(param)
+    {
+    case AL_BANDPASS_GAIN: *val = filter->Gain; return;
+    case AL_BANDPASS_GAINHF: *val = filter->GainHF; return;
+    case AL_BANDPASS_GAINLF: *val = filter->GainLF; return;
+    }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
+}
+template<>
+void FilterTable<BandpassFilterTable>::getParamfv(const ALfilter *filter, ALenum param, float *vals)
+{ getParamf(filter, param, vals); }
 
-    if UNLIKELY(n < 0)
-        context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
-    if UNLIKELY(n <= 0) return;
+
+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};
+    if(n <= 0) UNLIKELY return;
 
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{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"};
+
+    std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; });
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
+}
+
+AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters)
+FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n,
+    const ALuint *filters) noexcept
+try {
+    if(n < 0)
+        throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n};
+    if(n <= 0) UNLIKELY return;
+
+    ALCdevice *device{context->mALDevice.get()};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     /* First try to find any filters that are invalid. */
     auto validate_filter = [device](const ALuint fid) -> bool
     { return !fid || LookupFilter(device, fid) != nullptr; };
 
-    const ALuint *filters_end = filters + n;
-    auto invflt = std::find_if_not(filters, filters_end, validate_filter);
-    if UNLIKELY(invflt != filters_end)
-    {
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt);
-        return;
-    }
+    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};
 
     /* All good. Delete non-0 filter IDs. */
     auto delete_filter = [device](const ALuint fid) -> void
     {
-        ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr};
-        if(filter) FreeFilter(device, filter);
+        if(ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr})
+            FreeFilter(device, filter);
     };
-    std::for_each(filters, filters_end, delete_filter);
+    std::for_each(fids.begin(), fids.end(), delete_filter);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
-START_API_FUNC
+AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter)
+FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept
 {
-    ContextRef context{GetContextRef()};
-    if LIKELY(context)
-    {
-        ALCdevice *device{context->mALDevice.get()};
-        std::lock_guard<std::mutex> _{device->FilterLock};
-        if(!filter || LookupFilter(device, filter))
-            return AL_TRUE;
-    }
+    ALCdevice *device{context->mALDevice.get()};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
+    if(!filter || LookupFilter(device, filter))
+        return AL_TRUE;
     return AL_FALSE;
 }
-END_API_FUNC
-
 
-AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+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> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
+
+    switch(param)
     {
-        if(param == AL_FILTER_TYPE)
-        {
-            if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS
-                || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
-                InitFilterParams(alfilt, value);
-            else
-                context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
-        }
-        else try
-        {
-            /* Call the appropriate handler */
-            alfilt->setParami(param, value);
-        }
-        catch(filter_exception &e) {
-            context->setError(e.errorCode(), "%s", e.what());
-        }
+    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};
+        InitFilterParams(alfilt, value);
+        return;
     }
+
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
-START_API_FUNC
-{
+AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param,
+    const ALint *values) noexcept
+try {
     switch(param)
     {
     case AL_FILTER_TYPE:
-        alFilteri(filter, param, values[0]);
+        alFilteriDirect(context, filter, param, *values);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->setParamiv(param, values);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
-}
-END_API_FUNC
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
 
-AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", 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> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->setParamf(param, value);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
-}
-END_API_FUNC
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
 
-AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", 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> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->setParamfv(param, values);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
-}
-END_API_FUNC
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
 
-AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", 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> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     const ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
+
+    switch(param)
     {
-        if(param == AL_FILTER_TYPE)
-            *value = alfilt->type;
-        else try
-        {
-            /* Call the appropriate handler */
-            alfilt->getParami(param, value);
-        }
-        catch(filter_exception &e) {
-            context->setError(e.errorCode(), "%s", e.what());
-        }
+    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);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
-START_API_FUNC
-{
+AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param,
+    ALint *values) noexcept
+try {
     switch(param)
     {
     case AL_FILTER_TYPE:
-        alGetFilteri(filter, param, values);
+        alGetFilteriDirect(context, filter, param, values);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     const ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->getParamiv(param, values);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+    if(!alfilt)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
+
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+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};
+
+    const ALfilter *alfilt{LookupFilter(device, filter)};
+    if(!alfilt) UNLIKELY
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
+
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", 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> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
     const ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->getParamf(param, value);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+    if(!alfilt) UNLIKELY
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter};
+
+    /* Call the appropriate handler */
+    std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);},
+        alfilt->mTypeVariant);
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name)
+{
     ALCdevice *device{context->mALDevice.get()};
-    std::lock_guard<std::mutex> _{device->FilterLock};
+    std::lock_guard<std::mutex> filterlock{device->FilterLock};
 
-    const ALfilter *alfilt{LookupFilter(device, filter)};
-    if UNLIKELY(!alfilt)
-        context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
-    else try
-    {
-        /* Call the appropriate handler */
-        alfilt->getParamfv(param, values);
-    }
-    catch(filter_exception &e) {
-        context->setError(e.errorCode(), "%s", e.what());
-    }
+    auto filter = LookupFilter(device, id);
+    if(!filter)
+        throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id};
+
+    device->mFilterNames.insert_or_assign(id, name);
 }
-END_API_FUNC
 
 
 FilterSubList::~FilterSubList()
 {
+    if(!Filters)
+        return;
+
     uint64_t usemask{~FreeMask};
     while(usemask)
     {
         const int idx{al::countr_zero(usemask)};
-        al::destroy_at(Filters+idx);
+        std::destroy_at(al::to_address(Filters->begin() + idx));
         usemask &= ~(1_u64 << idx);
     }
     FreeMask = ~usemask;
-    al_free(Filters);
+    SubListAllocator{}.deallocate(Filters, 1);
     Filters = nullptr;
 }

+ 50 - 26
libs/openal-soft/al/filter.h

@@ -1,15 +1,40 @@
 #ifndef AL_FILTER_H
 #define AL_FILTER_H
 
+#include <array>
+#include <cstdint>
+#include <string_view>
+#include <utility>
+#include <variant>
 
 #include "AL/al.h"
 #include "AL/alc.h"
-#include "AL/alext.h"
+#include "AL/efx.h"
 
 #include "almalloc.h"
+#include "alnumeric.h"
 
-#define LOWPASSFREQREF  5000.0f
-#define HIGHPASSFREQREF  250.0f
+
+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*);
+};
+
+struct NullFilterTable : public FilterTable<NullFilterTable> { };
+struct LowpassFilterTable : public FilterTable<LowpassFilterTable> { };
+struct HighpassFilterTable : public FilterTable<HighpassFilterTable> { };
+struct BandpassFilterTable : public FilterTable<BandpassFilterTable> { };
 
 
 struct ALfilter {
@@ -17,36 +42,35 @@ struct ALfilter {
 
     float Gain{1.0f};
     float GainHF{1.0f};
-    float HFReference{LOWPASSFREQREF};
+    float HFReference{LowPassFreqRef};
     float GainLF{1.0f};
-    float LFReference{HIGHPASSFREQREF};
+    float LFReference{HighPassFreqRef};
 
-    struct Vtable {
-        void (*const setParami )(ALfilter *filter, ALenum param, int val);
-        void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals);
-        void (*const setParamf )(ALfilter *filter, ALenum param, float val);
-        void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals);
-
-        void (*const getParami )(const ALfilter *filter, ALenum param, int *val);
-        void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals);
-        void (*const getParamf )(const ALfilter *filter, ALenum param, float *val);
-        void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals);
-    };
-    const Vtable *vtab{nullptr};
+    using TableTypes = std::variant<NullFilterTable,LowpassFilterTable,HighpassFilterTable,
+        BandpassFilterTable>;
+    TableTypes mTypeVariant;
 
     /* Self ID */
     ALuint id{0};
 
-    void setParami(ALenum param, int value) { vtab->setParami(this, param, value); }
-    void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); }
-    void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); }
-    void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); }
-    void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); }
-    void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); }
-    void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); }
-    void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); }
+    static void SetName(ALCcontext *context, ALuint id, std::string_view name);
+
+    DISABLE_ALLOC
+};
+
+struct FilterSubList {
+    uint64_t FreeMask{~0_u64};
+    gsl::owner<std::array<ALfilter,64>*> Filters{nullptr};
+
+    FilterSubList() noexcept = default;
+    FilterSubList(const FilterSubList&) = delete;
+    FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
+    { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
+    ~FilterSubList();
 
-    DISABLE_ALLOC()
+    FilterSubList& operator=(const FilterSubList&) = delete;
+    FilterSubList& operator=(FilterSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
 };
 
 #endif

+ 210 - 257
libs/openal-soft/al/listener.cpp

@@ -22,6 +22,7 @@
 
 #include "listener.h"
 
+#include <algorithm>
 #include <cmath>
 #include <mutex>
 
@@ -30,9 +31,10 @@
 #include "AL/efx.h"
 
 #include "alc/context.h"
-#include "almalloc.h"
-#include "atomic.h"
-#include "core/except.h"
+#include "alc/inprogext.h"
+#include "alspan.h"
+#include "direct_defs.h"
+#include "error.h"
 #include "opthelpers.h"
 
 
@@ -48,402 +50,353 @@ inline void UpdateProps(ALCcontext *context)
     context->mPropsDirty = true;
 }
 
-#ifdef ALSOFT_EAX
 inline void CommitAndUpdateProps(ALCcontext *context)
 {
     if(!context->mDeferUpdates)
     {
-        if(context->has_eax())
+#ifdef ALSOFT_EAX
+        if(context->eaxNeedsCommit())
         {
-            context->mHoldUpdates.store(true, std::memory_order_release);
-            while((context->mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
-                /* busy-wait */
-            }
-
-            context->eax_commit_and_update_sources();
+            context->mPropsDirty = true;
+            context->applyAllUpdates();
+            return;
         }
+#endif
         UpdateContextProps(context);
-        context->mHoldUpdates.store(false, std::memory_order_release);
         return;
     }
     context->mPropsDirty = true;
 }
 
-#else
-
-inline void CommitAndUpdateProps(ALCcontext *context)
-{ UpdateProps(context); }
-#endif
-
 } // namespace
 
-AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
+AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value)
+FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept
+try {
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
     switch(param)
     {
     case AL_GAIN:
         if(!(value >= 0.0f && std::isfinite(value)))
-            SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
+            throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"};
         listener.Gain = value;
-        UpdateProps(context.get());
-        break;
+        UpdateProps(context);
+        return;
 
     case AL_METERS_PER_UNIT:
         if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
-            SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
+            throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"};
         listener.mMetersPerUnit = value;
-        UpdateProps(context.get());
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener float property");
+        UpdateProps(context);
+        return;
     }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
-AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
+AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3)
+FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1,
+    ALfloat value2, ALfloat value3) noexcept
+try {
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
     switch(param)
     {
     case AL_POSITION:
         if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
-            SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range");
+            throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"};
         listener.Position[0] = value1;
         listener.Position[1] = value2;
         listener.Position[2] = value3;
-        CommitAndUpdateProps(context.get());
-        break;
+        CommitAndUpdateProps(context);
+        return;
 
     case AL_VELOCITY:
         if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
-            SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range");
+            throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"};
         listener.Velocity[0] = value1;
         listener.Velocity[1] = value2;
         listener.Velocity[2] = value3;
-        CommitAndUpdateProps(context.get());
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
+        CommitAndUpdateProps(context);
+        return;
     }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
-START_API_FUNC
-{
-    if(values)
+AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values)
+FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param,
+    const ALfloat *values) noexcept
+try {
+    if(!values)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
+
+    switch(param)
     {
-        switch(param)
-        {
-        case AL_GAIN:
-        case AL_METERS_PER_UNIT:
-            alListenerf(param, values[0]);
-            return;
+    case AL_GAIN:
+    case AL_METERS_PER_UNIT:
+        alListenerfDirect(context, param, *values);
+        return;
 
-        case AL_POSITION:
-        case AL_VELOCITY:
-            alListener3f(param, values[0], values[1], values[2]);
-            return;
-        }
+    case AL_POSITION:
+    case AL_VELOCITY:
+        auto vals = al::span<const float,3>{values, 3_uz};
+        alListener3fDirect(context, param, vals[0], vals[1], vals[2]);
+        return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
     switch(param)
     {
     case AL_ORIENTATION:
-        if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
-             std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
-            SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range");
+        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");
         /* AT then UP */
-        listener.OrientAt[0] = values[0];
-        listener.OrientAt[1] = values[1];
-        listener.OrientAt[2] = values[2];
-        listener.OrientUp[0] = values[3];
-        listener.OrientUp[1] = values[4];
-        listener.OrientUp[2] = values[5];
-        CommitAndUpdateProps(context.get());
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
+        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};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
 
-AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    switch(param)
-    {
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
-    }
+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};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
-START_API_FUNC
-{
+AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3)
+FORCE_ALIGN void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1,
+    ALint value2, ALint value3) noexcept
+try {
     switch(param)
     {
     case AL_POSITION:
     case AL_VELOCITY:
-        alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
+        alListener3fDirect(context, param, static_cast<ALfloat>(value1),
+            static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    switch(param)
-    {
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
-    }
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
-START_API_FUNC
-{
-    if(values)
+AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param,
+    const ALint *values) noexcept
+try {
+    if(!values)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
+
+    al::span<const ALint> vals;
+    switch(param)
     {
-        ALfloat fvals[6];
-        switch(param)
-        {
-        case AL_POSITION:
-        case AL_VELOCITY:
-            alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]), static_cast<ALfloat>(values[2]));
-            return;
+    case AL_POSITION:
+    case AL_VELOCITY:
+        vals = {values, 3_uz};
+        alListener3fDirect(context, param, static_cast<ALfloat>(vals[0]),
+            static_cast<ALfloat>(vals[1]), static_cast<ALfloat>(vals[2]));
+        return;
 
-        case AL_ORIENTATION:
-            fvals[0] = static_cast<ALfloat>(values[0]);
-            fvals[1] = static_cast<ALfloat>(values[1]);
-            fvals[2] = static_cast<ALfloat>(values[2]);
-            fvals[3] = static_cast<ALfloat>(values[3]);
-            fvals[4] = static_cast<ALfloat>(values[4]);
-            fvals[5] = static_cast<ALfloat>(values[5]);
-            alListenerfv(param, fvals);
-            return;
-        }
+    case AL_ORIENTATION:
+        vals = {values, 6_uz};
+        const std::array fvals{static_cast<ALfloat>(vals[0]), static_cast<ALfloat>(vals[1]),
+            static_cast<ALfloat>(vals[2]), static_cast<ALfloat>(vals[3]),
+            static_cast<ALfloat>(vals[4]), static_cast<ALfloat>(vals[5]),
+        };
+        alListenerfvDirect(context, param, fvals.data());
+        return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!values)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
-    {
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
-    }
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x",
+        param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
 
-AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value)
+FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param,
+    ALfloat *value) noexcept
+try {
+    if(!value)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
 
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!value)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    switch(param)
     {
-    case AL_GAIN:
-        *value = listener.Gain;
-        break;
-
-    case AL_METERS_PER_UNIT:
-        *value = listener.mMetersPerUnit;
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener float property");
+    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};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3)
+FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param,
+    ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept
+try {
+    if(!value1 || !value2 || !value3)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
 
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!value1 || !value2 || !value3)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    switch(param)
     {
     case AL_POSITION:
         *value1 = listener.Position[0];
         *value2 = listener.Position[1];
         *value3 = listener.Position[2];
-        break;
+        return;
 
     case AL_VELOCITY:
         *value1 = listener.Velocity[0];
         *value2 = listener.Velocity[1];
         *value3 = listener.Velocity[2];
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
+        return;
     }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
-START_API_FUNC
-{
+AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values)
+FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param,
+    ALfloat *values) noexcept
+try {
+    if(!values)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
+
     switch(param)
     {
     case AL_GAIN:
     case AL_METERS_PER_UNIT:
-        alGetListenerf(param, values);
+        alGetListenerfDirect(context, param, values);
         return;
 
     case AL_POSITION:
     case AL_VELOCITY:
-        alGetListener3f(param, values+0, values+1, values+2);
+        auto vals = al::span<ALfloat,3>{values, 3_uz};
+        alGetListener3fDirect(context, param, &vals[0], &vals[1], &vals[2]);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!values)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    switch(param)
     {
     case AL_ORIENTATION:
+        al::span<ALfloat,6> vals{values, 6_uz};
         // AT then UP
-        values[0] = listener.OrientAt[0];
-        values[1] = listener.OrientAt[1];
-        values[2] = listener.OrientAt[2];
-        values[3] = listener.OrientUp[0];
-        values[4] = listener.OrientUp[1];
-        values[5] = listener.OrientUp[2];
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
+        std::copy_n(listener.OrientAt.cbegin(), 3, vals.begin());
+        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};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
-
 
-AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
 
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!value)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
-    {
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
-    }
+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"};
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
-START_API_FUNC
-{
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
+AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3)
+FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param,
+    ALint *value1, ALint *value2, ALint *value3) noexcept
+try {
+    if(!value1 || !value2 || !value3)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
 
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!value1 || !value2 || !value3)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+    switch(param)
     {
     case AL_POSITION:
         *value1 = static_cast<ALint>(listener.Position[0]);
         *value2 = static_cast<ALint>(listener.Position[1]);
         *value3 = static_cast<ALint>(listener.Position[2]);
-        break;
+        return;
 
     case AL_VELOCITY:
         *value1 = static_cast<ALint>(listener.Velocity[0]);
         *value2 = static_cast<ALint>(listener.Velocity[1]);
         *value3 = static_cast<ALint>(listener.Velocity[2]);
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
+        return;
     }
+    throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC
 
-AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
-START_API_FUNC
-{
+AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values)
+FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param,
+    ALint *values) noexcept
+try {
+    if(!values)
+        throw al::context_error{AL_INVALID_VALUE, "NULL pointer"};
+
     switch(param)
     {
     case AL_POSITION:
     case AL_VELOCITY:
-        alGetListener3i(param, values+0, values+1, values+2);
+        auto vals = al::span<ALint,3>{values, 3_uz};
+        alGetListener3iDirect(context, param, &vals[0], &vals[1], &vals[2]);
         return;
     }
 
-    ContextRef context{GetContextRef()};
-    if UNLIKELY(!context) return;
-
     ALlistener &listener = context->mListener;
-    std::lock_guard<std::mutex> _{context->mPropLock};
-    if(!values)
-        context->setError(AL_INVALID_VALUE, "NULL pointer");
-    else switch(param)
+    std::lock_guard<std::mutex> proplock{context->mPropLock};
+
+    static constexpr auto f2i = [](const float val) noexcept { return static_cast<ALint>(val); };
+    switch(param)
     {
     case AL_ORIENTATION:
+        auto vals = al::span<ALint,6>{values, 6_uz};
         // AT then UP
-        values[0] = static_cast<ALint>(listener.OrientAt[0]);
-        values[1] = static_cast<ALint>(listener.OrientAt[1]);
-        values[2] = static_cast<ALint>(listener.OrientAt[2]);
-        values[3] = static_cast<ALint>(listener.OrientUp[0]);
-        values[4] = static_cast<ALint>(listener.OrientUp[1]);
-        values[5] = static_cast<ALint>(listener.OrientUp[2]);
-        break;
-
-    default:
-        context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
+        std::transform(listener.OrientAt.cbegin(), listener.OrientAt.cend(), vals.begin(), f2i);
+        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};
+}
+catch(al::context_error& e) {
+    context->setError(e.errorCode(), "%s", e.what());
 }
-END_API_FUNC

+ 1 - 3
libs/openal-soft/al/listener.h

@@ -3,8 +3,6 @@
 
 #include <array>
 
-#include "AL/al.h"
-#include "AL/alc.h"
 #include "AL/efx.h"
 
 #include "almalloc.h"
@@ -18,7 +16,7 @@ struct ALlistener {
     float Gain{1.0f};
     float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
 
-    DISABLE_ALLOC()
+    DISABLE_ALLOC
 };
 
 #endif

文件差异内容过多而无法显示
+ 415 - 210
libs/openal-soft/al/source.cpp


+ 829 - 584
libs/openal-soft/al/source.h

@@ -2,113 +2,61 @@
 #define AL_SOURCE_H
 
 #include <array>
-#include <atomic>
 #include <cstddef>
-#include <iterator>
-#include <limits>
+#include <cstdint>
 #include <deque>
+#include <limits>
+#include <string_view>
+#include <utility>
 
 #include "AL/al.h"
 #include "AL/alc.h"
+#include "AL/alext.h"
 
-#include "alc/alu.h"
-#include "alc/context.h"
-#include "alc/inprogext.h"
-#include "aldeque.h"
 #include "almalloc.h"
+#include "alnumbers.h"
 #include "alnumeric.h"
-#include "atomic.h"
+#include "alspan.h"
+#include "core/context.h"
 #include "core/voice.h"
-#include "vector.h"
 
 #ifdef ALSOFT_EAX
-#include "eax_eax_call.h"
-#include "eax_fx_slot_index.h"
-#include "eax_utils.h"
+#include "eax/api.h"
+#include "eax/call.h"
+#include "eax/exception.h"
+#include "eax/fx_slot_index.h"
+#include "eax/utils.h"
 #endif // ALSOFT_EAX
 
 struct ALbuffer;
 struct ALeffectslot;
-
+enum class Resampler : uint8_t;
 
 enum class SourceStereo : bool {
     Normal = AL_NORMAL_SOFT,
     Enhanced = AL_SUPER_STEREO_SOFT
 };
 
-#define DEFAULT_SENDS  2
+inline constexpr size_t DefaultSendCount{2};
 
-#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
+inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits<ALuint>::max()};
+
+inline bool sBufferSubDataCompat{false};
 
 struct ALbufferQueueItem : public VoiceBufferItem {
     ALbuffer *mBuffer{nullptr};
 
-    DISABLE_ALLOC()
+    DISABLE_ALLOC
 };
 
 
 #ifdef ALSOFT_EAX
-using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t;
-
-struct EaxSourceSourceFilterDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-	EaxSourceSourceFilterDirtyFlagsValue lDirect : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lRoom : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1;
-	EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1;
-	EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1;
-	EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1;
-	EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1;
-	EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1;
-	EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1;
-}; // EaxSourceSourceFilterDirtyFlags
-
-
-using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxSourceSourceMiscDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-	EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1;
-	EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1;
-	EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1;
-	EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1;
-	EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1;
-	EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1;
-	EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1;
-	EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1;
-}; // EaxSourceSourceMiscDirtyFlags
-
-
-using EaxSourceSendDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxSourceSendDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-	EaxSourceSendDirtyFlagsValue lSend : 1;
-	EaxSourceSendDirtyFlagsValue lSendHF : 1;
-	EaxSourceSendDirtyFlagsValue lOcclusion : 1;
-	EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1;
-	EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1;
-	EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1;
-	EaxSourceSendDirtyFlagsValue lExclusion : 1;
-	EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1;
-}; // EaxSourceSendDirtyFlags
-
-
-struct EaxSourceSendsDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-	EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS];
-}; // EaxSourceSendsDirtyFlags
+class EaxSourceException : public EaxException {
+public:
+    explicit EaxSourceException(const char* message)
+        : EaxException{"EAX_SOURCE", message}
+    {}
+};
 #endif // ALSOFT_EAX
 
 struct ALsource {
@@ -140,6 +88,7 @@ struct ALsource {
     DirectMode DirectChannels{DirectMode::Off};
     SpatializeMode mSpatialize{SpatializeMode::Auto};
     SourceStereo mStereoMode{SourceStereo::Normal};
+    bool mPanningEnabled{false};
 
     bool DryGainHFAuto{true};
     bool WetGainAuto{true};
@@ -157,24 +106,27 @@ struct ALsource {
 
     float Radius{0.0f};
     float EnhWidth{0.593f};
+    float mPan{0.0f};
 
     /** Direct filter and auxiliary send info. */
-    struct {
-        float Gain;
-        float GainHF;
-        float HFReference;
-        float GainLF;
-        float LFReference;
-    } Direct;
+    struct DirectData {
+        float Gain{};
+        float GainHF{};
+        float HFReference{};
+        float GainLF{};
+        float LFReference{};
+    };
+    DirectData Direct;
+
     struct SendData {
-        ALeffectslot *Slot;
-        float Gain;
-        float GainHF;
-        float HFReference;
-        float GainLF;
-        float LFReference;
+        ALeffectslot *Slot{};
+        float Gain{};
+        float GainHF{};
+        float HFReference{};
+        float GainLF{};
+        float LFReference{};
     };
-    std::array<SendData,MAX_SENDS> Send;
+    std::array<SendData,MaxSendCount> Send;
 
     /**
      * Last user-specified offset, and the offset type (bytes, samples, or
@@ -190,620 +142,913 @@ struct ALsource {
     ALenum state{AL_INITIAL};
 
     /** Source Buffer Queue head. */
-    al::deque<ALbufferQueueItem> mQueue;
+    std::deque<ALbufferQueueItem> mQueue;
 
     bool mPropsDirty{true};
 
     /* Index into the context's Voices array. Lazily updated, only checked and
      * reset when looking up the voice.
      */
-    ALuint VoiceIdx{INVALID_VOICE_IDX};
+    ALuint VoiceIdx{InvalidVoiceIndex};
 
     /** Self ID */
     ALuint id{0};
 
 
-    ALsource();
+    ALsource() noexcept;
     ~ALsource();
 
     ALsource(const ALsource&) = delete;
     ALsource& operator=(const ALsource&) = delete;
 
-    DISABLE_ALLOC()
+    static void SetName(ALCcontext *context, ALuint id, std::string_view name);
+
+    DISABLE_ALLOC
 
 #ifdef ALSOFT_EAX
 public:
-    void eax_initialize(ALCcontext *context) noexcept;
-
-
-    void eax_dispatch(const EaxEaxCall& eax_call)
-    { eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); }
-
-
-    void eax_update_filters();
-
-    void eax_update(
-        EaxContextSharedDirtyFlags dirty_flags);
-
-    void eax_commit() { eax_apply_deferred(); }
-    void eax_commit_and_update();
-
-    bool eax_is_initialized() const noexcept { return eax_al_context_; }
-
-
-    static ALsource* eax_lookup_source(
-        ALCcontext& al_context,
-        ALuint source_id) noexcept;
+    void eaxInitialize(ALCcontext *context) noexcept;
+    void eaxDispatch(const EaxCall& call);
+    void eaxCommit();
+    void eaxMarkAsChanged() noexcept { mEaxChanged = true; }
 
+    static ALsource* EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept;
 
 private:
-    static constexpr auto eax_max_speakers = 9;
-
-
-    using EaxActiveFxSlots = std::array<bool, EAX_MAX_FXSLOTS>;
-    using EaxSpeakerLevels = std::array<long, eax_max_speakers>;
-
-    struct Eax
-    {
-        using Sends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
-
-        EAX50ACTIVEFXSLOTS active_fx_slots{};
-        EAX50SOURCEPROPERTIES source{};
-        Sends sends{};
-        EaxSpeakerLevels speaker_levels{};
-    }; // Eax
-
-
-    bool eax_uses_primary_id_{};
-    bool eax_has_active_fx_slots_{};
-    bool eax_are_active_fx_slots_dirty_{};
-
-    ALCcontext* eax_al_context_{};
-
-    EAXBUFFER_REVERBPROPERTIES eax1_{};
-    Eax eax_{};
-    Eax eax_d_{};
-    EaxActiveFxSlots eax_active_fx_slots_{};
-
-    EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{};
-    EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{};
-    EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{};
-
-
-    [[noreturn]]
-    static void eax_fail(
-        const char* message);
-
-
-    void eax_set_source_defaults() noexcept;
-    void eax_set_active_fx_slots_defaults() noexcept;
-    void eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept;
-    void eax_set_sends_defaults() noexcept;
-    void eax_set_speaker_levels_defaults() noexcept;
-    void eax_set_defaults() noexcept;
-
-
-    static float eax_calculate_dst_occlusion_mb(
-        long src_occlusion_mb,
-        float path_ratio,
-        float lf_ratio) noexcept;
-
-    EaxAlLowPassParam eax_create_direct_filter_param() const noexcept;
-
-    EaxAlLowPassParam eax_create_room_filter_param(
-        const ALeffectslot& fx_slot,
-        const EAXSOURCEALLSENDPROPERTIES& send) const noexcept;
-
-    void eax_set_fx_slots();
-
-    void eax_initialize_fx_slots();
-
-    void eax_update_direct_filter_internal();
-
-    void eax_update_room_filters_internal();
-
-    void eax_update_filters_internal();
-
-    void eax_update_primary_fx_slot_id();
-
-
-    void eax_defer_active_fx_slots(
-        const EaxEaxCall& eax_call);
-
-
-    static const char* eax_get_exclusion_name() noexcept;
-
-    static const char* eax_get_exclusion_lf_ratio_name() noexcept;
-
-
-    static const char* eax_get_occlusion_name() noexcept;
-
-    static const char* eax_get_occlusion_lf_ratio_name() noexcept;
-
-    static const char* eax_get_occlusion_direct_ratio_name() noexcept;
-
-    static const char* eax_get_occlusion_room_ratio_name() noexcept;
-
-
-    static void eax1_validate_reverb_mix(float reverb_mix);
-
-    static void eax_validate_send_receiving_fx_slot_guid(
-        const GUID& guidReceivingFXSlotID);
-
-    static void eax_validate_send_send(
-        long lSend);
-
-    static void eax_validate_send_send_hf(
-        long lSendHF);
-
-    static void eax_validate_send_occlusion(
-        long lOcclusion);
-
-    static void eax_validate_send_occlusion_lf_ratio(
-        float flOcclusionLFRatio);
-
-    static void eax_validate_send_occlusion_room_ratio(
-        float flOcclusionRoomRatio);
-
-    static void eax_validate_send_occlusion_direct_ratio(
-        float flOcclusionDirectRatio);
-
-    static void eax_validate_send_exclusion(
-        long lExclusion);
-
-    static void eax_validate_send_exclusion_lf_ratio(
-        float flExclusionLFRatio);
-
-    static void eax_validate_send(
-        const EAXSOURCESENDPROPERTIES& all);
-
-    static void eax_validate_send_exclusion_all(
-        const EAXSOURCEEXCLUSIONSENDPROPERTIES& all);
-
-    static void eax_validate_send_occlusion_all(
-        const EAXSOURCEOCCLUSIONSENDPROPERTIES& all);
-
-    static void eax_validate_send_all(
-        const EAXSOURCEALLSENDPROPERTIES& all);
-
-
-    static EaxFxSlotIndexValue eax_get_send_index(
-        const GUID& send_guid);
-
-
-    void eax_defer_send_send(
-        long lSend,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_send_hf(
-        long lSendHF,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_occlusion(
-        long lOcclusion,
-        EaxFxSlotIndexValue index);
+    using Exception = EaxSourceException;
 
-    void eax_defer_send_occlusion_lf_ratio(
-        float flOcclusionLFRatio,
-        EaxFxSlotIndexValue index);
+    static constexpr auto eax_max_speakers{9u};
 
-    void eax_defer_send_occlusion_room_ratio(
-        float flOcclusionRoomRatio,
-        EaxFxSlotIndexValue index);
+    using EaxFxSlotIds = std::array<const GUID*,EAX_MAX_FXSLOTS>;
 
-    void eax_defer_send_occlusion_direct_ratio(
-        float flOcclusionDirectRatio,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_exclusion(
-        long lExclusion,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_exclusion_lf_ratio(
-        float flExclusionLFRatio,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send(
-        const EAXSOURCESENDPROPERTIES& all,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_exclusion_all(
-        const EAXSOURCEEXCLUSIONSENDPROPERTIES& all,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_occlusion_all(
-        const EAXSOURCEOCCLUSIONSENDPROPERTIES& all,
-        EaxFxSlotIndexValue index);
-
-    void eax_defer_send_all(
-        const EAXSOURCEALLSENDPROPERTIES& all,
-        EaxFxSlotIndexValue index);
-
-
-    void eax_defer_send(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_send_exclusion_all(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_send_occlusion_all(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_send_all(
-        const EaxEaxCall& eax_call);
-
-
-    static void eax_validate_source_direct(
-        long direct);
-
-    static void eax_validate_source_direct_hf(
-        long direct_hf);
-
-    static void eax_validate_source_room(
-        long room);
-
-    static void eax_validate_source_room_hf(
-        long room_hf);
-
-    static void eax_validate_source_obstruction(
-        long obstruction);
-
-    static void eax_validate_source_obstruction_lf_ratio(
-        float obstruction_lf_ratio);
-
-    static void eax_validate_source_occlusion(
-        long occlusion);
-
-    static void eax_validate_source_occlusion_lf_ratio(
-        float occlusion_lf_ratio);
-
-    static void eax_validate_source_occlusion_room_ratio(
-        float occlusion_room_ratio);
-
-    static void eax_validate_source_occlusion_direct_ratio(
-        float occlusion_direct_ratio);
-
-    static void eax_validate_source_exclusion(
-        long exclusion);
-
-    static void eax_validate_source_exclusion_lf_ratio(
-        float exclusion_lf_ratio);
-
-    static void eax_validate_source_outside_volume_hf(
-        long outside_volume_hf);
-
-    static void eax_validate_source_doppler_factor(
-        float doppler_factor);
-
-    static void eax_validate_source_rolloff_factor(
-        float rolloff_factor);
-
-    static void eax_validate_source_room_rolloff_factor(
-        float room_rolloff_factor);
-
-    static void eax_validate_source_air_absorption_factor(
-        float air_absorption_factor);
-
-    static void eax_validate_source_flags(
-        unsigned long flags,
-        int eax_version);
-
-    static void eax_validate_source_macro_fx_factor(
-        float macro_fx_factor);
-
-    static void eax_validate_source_2d_all(
-        const EAXSOURCE2DPROPERTIES& all,
-        int eax_version);
-
-    static void eax_validate_source_obstruction_all(
-        const EAXOBSTRUCTIONPROPERTIES& all);
-
-    static void eax_validate_source_exclusion_all(
-        const EAXEXCLUSIONPROPERTIES& all);
-
-    static void eax_validate_source_occlusion_all(
-        const EAXOCCLUSIONPROPERTIES& all);
-
-    static void eax_validate_source_all(
-        const EAX20BUFFERPROPERTIES& all,
-        int eax_version);
-
-    static void eax_validate_source_all(
-        const EAX30SOURCEPROPERTIES& all,
-        int eax_version);
-
-    static void eax_validate_source_all(
-        const EAX50SOURCEPROPERTIES& all,
-        int eax_version);
-
-    static void eax_validate_source_speaker_id(
-        long speaker_id);
-
-    static void eax_validate_source_speaker_level(
-        long speaker_level);
-
-    static void eax_validate_source_speaker_level_all(
-        const EAXSPEAKERLEVELPROPERTIES& all);
-
-
-    void eax_defer_source_direct(
-        long lDirect);
-
-    void eax_defer_source_direct_hf(
-        long lDirectHF);
-
-    void eax_defer_source_room(
-        long lRoom);
-
-    void eax_defer_source_room_hf(
-        long lRoomHF);
-
-    void eax_defer_source_obstruction(
-        long lObstruction);
-
-    void eax_defer_source_obstruction_lf_ratio(
-        float flObstructionLFRatio);
-
-    void eax_defer_source_occlusion(
-        long lOcclusion);
-
-    void eax_defer_source_occlusion_lf_ratio(
-        float flOcclusionLFRatio);
+    static constexpr const EaxFxSlotIds eax4_fx_slot_ids{
+        &EAXPROPERTYID_EAX40_FXSlot0,
+        &EAXPROPERTYID_EAX40_FXSlot1,
+        &EAXPROPERTYID_EAX40_FXSlot2,
+        &EAXPROPERTYID_EAX40_FXSlot3,
+    };
 
-    void eax_defer_source_occlusion_room_ratio(
-        float flOcclusionRoomRatio);
+    static constexpr const EaxFxSlotIds eax5_fx_slot_ids{
+        &EAXPROPERTYID_EAX50_FXSlot0,
+        &EAXPROPERTYID_EAX50_FXSlot1,
+        &EAXPROPERTYID_EAX50_FXSlot2,
+        &EAXPROPERTYID_EAX50_FXSlot3,
+    };
 
-    void eax_defer_source_occlusion_direct_ratio(
-        float flOcclusionDirectRatio);
+    using EaxActiveFxSlots = std::array<bool, EAX_MAX_FXSLOTS>;
+    using EaxSpeakerLevels = std::array<EAXSPEAKERLEVELPROPERTIES, eax_max_speakers>;
+    using EaxSends = std::array<EAXSOURCEALLSENDPROPERTIES, EAX_MAX_FXSLOTS>;
 
-    void eax_defer_source_exclusion(
-        long lExclusion);
+    using Eax1Props = EAXBUFFER_REVERBPROPERTIES;
+    struct Eax1State {
+        Eax1Props i; // Immediate.
+        Eax1Props d; // Deferred.
+    };
 
-    void eax_defer_source_exclusion_lf_ratio(
-        float flExclusionLFRatio);
+    using Eax2Props = EAX20BUFFERPROPERTIES;
+    struct Eax2State {
+        Eax2Props i; // Immediate.
+        Eax2Props d; // Deferred.
+    };
 
-    void eax_defer_source_outside_volume_hf(
-        long lOutsideVolumeHF);
+    using Eax3Props = EAX30SOURCEPROPERTIES;
+    struct Eax3State {
+        Eax3Props i; // Immediate.
+        Eax3Props d; // Deferred.
+    };
 
-    void eax_defer_source_doppler_factor(
-        float flDopplerFactor);
+    struct Eax4Props {
+        Eax3Props source;
+        EaxSends sends;
+        EAX40ACTIVEFXSLOTS active_fx_slots;
+    };
 
-    void eax_defer_source_rolloff_factor(
-        float flRolloffFactor);
+    struct Eax4State {
+        Eax4Props i; // Immediate.
+        Eax4Props d; // Deferred.
+    };
 
-    void eax_defer_source_room_rolloff_factor(
-        float flRoomRolloffFactor);
+    struct Eax5Props {
+        EAX50SOURCEPROPERTIES source;
+        EaxSends sends;
+        EAX50ACTIVEFXSLOTS active_fx_slots;
+        EaxSpeakerLevels speaker_levels;
+    };
 
-    void eax_defer_source_air_absorption_factor(
-        float flAirAbsorptionFactor);
+    struct Eax5State {
+        Eax5Props i; // Immediate.
+        Eax5Props d; // Deferred.
+    };
 
-    void eax_defer_source_flags(
-        unsigned long ulFlags);
+    ALCcontext* mEaxAlContext{};
+    EaxFxSlotIndex mEaxPrimaryFxSlotId{};
+    EaxActiveFxSlots mEaxActiveFxSlots{};
+    int mEaxVersion{};
+    bool mEaxChanged{};
+    Eax1State mEax1{};
+    Eax2State mEax2{};
+    Eax3State mEax3{};
+    Eax4State mEax4{};
+    Eax5State mEax5{};
+    Eax5Props mEax{};
+
+    // ----------------------------------------------------------------------
+    // Source validators
+
+    struct Eax1SourceReverbMixValidator {
+        void operator()(float reverb_mix) const
+        {
+            if (reverb_mix == EAX_REVERBMIX_USEDISTANCE)
+                return;
+
+            eax_validate_range<Exception>(
+                "Reverb Mix",
+                reverb_mix,
+                EAX_BUFFER_MINREVERBMIX,
+                EAX_BUFFER_MAXREVERBMIX);
+        }
+    };
 
-    void eax_defer_source_macro_fx_factor(
-        float flMacroFXFactor);
+    struct Eax2SourceDirectValidator {
+        void operator()(long lDirect) const
+        {
+            eax_validate_range<Exception>(
+                "Direct",
+                lDirect,
+                EAXSOURCE_MINDIRECT,
+                EAXSOURCE_MAXDIRECT);
+        }
+    };
 
-    void eax_defer_source_2d_all(
-        const EAXSOURCE2DPROPERTIES& all);
+    struct Eax2SourceDirectHfValidator {
+        void operator()(long lDirectHF) const
+        {
+            eax_validate_range<Exception>(
+                "Direct HF",
+                lDirectHF,
+                EAXSOURCE_MINDIRECTHF,
+                EAXSOURCE_MAXDIRECTHF);
+        }
+    };
 
-    void eax_defer_source_obstruction_all(
-        const EAXOBSTRUCTIONPROPERTIES& all);
+    struct Eax2SourceRoomValidator {
+        void operator()(long lRoom) const
+        {
+            eax_validate_range<Exception>(
+                "Room",
+                lRoom,
+                EAXSOURCE_MINROOM,
+                EAXSOURCE_MAXROOM);
+        }
+    };
 
-    void eax_defer_source_exclusion_all(
-        const EAXEXCLUSIONPROPERTIES& all);
+    struct Eax2SourceRoomHfValidator {
+        void operator()(long lRoomHF) const
+        {
+            eax_validate_range<Exception>(
+                "Room HF",
+                lRoomHF,
+                EAXSOURCE_MINROOMHF,
+                EAXSOURCE_MAXROOMHF);
+        }
+    };
 
-    void eax_defer_source_occlusion_all(
-        const EAXOCCLUSIONPROPERTIES& all);
+    struct Eax2SourceRoomRolloffFactorValidator {
+        void operator()(float flRoomRolloffFactor) const
+        {
+            eax_validate_range<Exception>(
+                "Room Rolloff Factor",
+                flRoomRolloffFactor,
+                EAXSOURCE_MINROOMROLLOFFFACTOR,
+                EAXSOURCE_MAXROOMROLLOFFFACTOR);
+        }
+    };
 
-    void eax_defer_source_all(
-        const EAX20BUFFERPROPERTIES& all);
+    struct Eax2SourceObstructionValidator {
+        void operator()(long lObstruction) const
+        {
+            eax_validate_range<Exception>(
+                "Obstruction",
+                lObstruction,
+                EAXSOURCE_MINOBSTRUCTION,
+                EAXSOURCE_MAXOBSTRUCTION);
+        }
+    };
 
-    void eax_defer_source_all(
-        const EAX30SOURCEPROPERTIES& all);
+    struct Eax2SourceObstructionLfRatioValidator {
+        void operator()(float flObstructionLFRatio) const
+        {
+            eax_validate_range<Exception>(
+                "Obstruction LF Ratio",
+                flObstructionLFRatio,
+                EAXSOURCE_MINOBSTRUCTIONLFRATIO,
+                EAXSOURCE_MAXOBSTRUCTIONLFRATIO);
+        }
+    };
 
-    void eax_defer_source_all(
-        const EAX50SOURCEPROPERTIES& all);
+    struct Eax2SourceOcclusionValidator {
+        void operator()(long lOcclusion) const
+        {
+            eax_validate_range<Exception>(
+                "Occlusion",
+                lOcclusion,
+                EAXSOURCE_MINOCCLUSION,
+                EAXSOURCE_MAXOCCLUSION);
+        }
+    };
 
-    void eax_defer_source_speaker_level_all(
-        const EAXSPEAKERLEVELPROPERTIES& all);
+    struct Eax2SourceOcclusionLfRatioValidator {
+        void operator()(float flOcclusionLFRatio) const
+        {
+            eax_validate_range<Exception>(
+                "Occlusion LF Ratio",
+                flOcclusionLFRatio,
+                EAXSOURCE_MINOCCLUSIONLFRATIO,
+                EAXSOURCE_MAXOCCLUSIONLFRATIO);
+        }
+    };
 
+    struct Eax2SourceOcclusionRoomRatioValidator {
+        void operator()(float flOcclusionRoomRatio) const
+        {
+            eax_validate_range<Exception>(
+                "Occlusion Room Ratio",
+                flOcclusionRoomRatio,
+                EAXSOURCE_MINOCCLUSIONROOMRATIO,
+                EAXSOURCE_MAXOCCLUSIONROOMRATIO);
+        }
+    };
 
-    void eax_defer_source_direct(
-        const EaxEaxCall& eax_call);
+    struct Eax2SourceOutsideVolumeHfValidator {
+        void operator()(long lOutsideVolumeHF) const
+        {
+            eax_validate_range<Exception>(
+                "Outside Volume HF",
+                lOutsideVolumeHF,
+                EAXSOURCE_MINOUTSIDEVOLUMEHF,
+                EAXSOURCE_MAXOUTSIDEVOLUMEHF);
+        }
+    };
 
-    void eax_defer_source_direct_hf(
-        const EaxEaxCall& eax_call);
+    struct Eax2SourceAirAbsorptionFactorValidator {
+        void operator()(float flAirAbsorptionFactor) const
+        {
+            eax_validate_range<Exception>(
+                "Air Absorption Factor",
+                flAirAbsorptionFactor,
+                EAXSOURCE_MINAIRABSORPTIONFACTOR,
+                EAXSOURCE_MAXAIRABSORPTIONFACTOR);
+        }
+    };
 
-    void eax_defer_source_room(
-        const EaxEaxCall& eax_call);
+    struct Eax2SourceFlagsValidator {
+        void operator()(unsigned long dwFlags) const
+        {
+            eax_validate_range<Exception>(
+                "Flags",
+                dwFlags,
+                0UL,
+                ~EAX20SOURCEFLAGS_RESERVED);
+        }
+    };
 
-    void eax_defer_source_room_hf(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceOcclusionDirectRatioValidator {
+        void operator()(float flOcclusionDirectRatio) const
+        {
+            eax_validate_range<Exception>(
+                "Occlusion Direct Ratio",
+                flOcclusionDirectRatio,
+                EAXSOURCE_MINOCCLUSIONDIRECTRATIO,
+                EAXSOURCE_MAXOCCLUSIONDIRECTRATIO);
+        }
+    };
 
-    void eax_defer_source_obstruction(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceExclusionValidator {
+        void operator()(long lExclusion) const
+        {
+            eax_validate_range<Exception>(
+                "Exclusion",
+                lExclusion,
+                EAXSOURCE_MINEXCLUSION,
+                EAXSOURCE_MAXEXCLUSION);
+        }
+    };
 
-    void eax_defer_source_obstruction_lf_ratio(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceExclusionLfRatioValidator {
+        void operator()(float flExclusionLFRatio) const
+        {
+            eax_validate_range<Exception>(
+                "Exclusion LF Ratio",
+                flExclusionLFRatio,
+                EAXSOURCE_MINEXCLUSIONLFRATIO,
+                EAXSOURCE_MAXEXCLUSIONLFRATIO);
+        }
+    };
 
-    void eax_defer_source_occlusion(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceDopplerFactorValidator {
+        void operator()(float flDopplerFactor) const
+        {
+            eax_validate_range<Exception>(
+                "Doppler Factor",
+                flDopplerFactor,
+                EAXSOURCE_MINDOPPLERFACTOR,
+                EAXSOURCE_MAXDOPPLERFACTOR);
+        }
+    };
 
-    void eax_defer_source_occlusion_lf_ratio(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceRolloffFactorValidator {
+        void operator()(float flRolloffFactor) const
+        {
+            eax_validate_range<Exception>(
+                "Rolloff Factor",
+                flRolloffFactor,
+                EAXSOURCE_MINROLLOFFFACTOR,
+                EAXSOURCE_MAXROLLOFFFACTOR);
+        }
+    };
 
-    void eax_defer_source_occlusion_room_ratio(
-        const EaxEaxCall& eax_call);
+    struct Eax5SourceMacroFXFactorValidator {
+        void operator()(float flMacroFXFactor) const
+        {
+            eax_validate_range<Exception>(
+                "Macro FX Factor",
+                flMacroFXFactor,
+                EAXSOURCE_MINMACROFXFACTOR,
+                EAXSOURCE_MAXMACROFXFACTOR);
+        }
+    };
 
-    void eax_defer_source_occlusion_direct_ratio(
-        const EaxEaxCall& eax_call);
+    struct Eax5SourceFlagsValidator {
+        void operator()(unsigned long dwFlags) const
+        {
+            eax_validate_range<Exception>(
+                "Flags",
+                dwFlags,
+                0UL,
+                ~EAX50SOURCEFLAGS_RESERVED);
+        }
+    };
 
-    void eax_defer_source_exclusion(
-        const EaxEaxCall& eax_call);
+    struct Eax1SourceAllValidator {
+        void operator()(const Eax1Props& props) const
+        {
+            Eax1SourceReverbMixValidator{}(props.fMix);
+        }
+    };
 
-    void eax_defer_source_exclusion_lf_ratio(
-        const EaxEaxCall& eax_call);
+    struct Eax2SourceAllValidator {
+        void operator()(const Eax2Props& props) const
+        {
+            Eax2SourceDirectValidator{}(props.lDirect);
+            Eax2SourceDirectHfValidator{}(props.lDirectHF);
+            Eax2SourceRoomValidator{}(props.lRoom);
+            Eax2SourceRoomHfValidator{}(props.lRoomHF);
+            Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor);
+            Eax2SourceObstructionValidator{}(props.lObstruction);
+            Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio);
+            Eax2SourceOcclusionValidator{}(props.lOcclusion);
+            Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
+            Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
+            Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF);
+            Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor);
+            Eax2SourceFlagsValidator{}(props.dwFlags);
+        }
+    };
 
-    void eax_defer_source_outside_volume_hf(
-        const EaxEaxCall& eax_call);
+    struct Eax3SourceAllValidator {
+        void operator()(const Eax3Props& props) const
+        {
+            Eax2SourceDirectValidator{}(props.lDirect);
+            Eax2SourceDirectHfValidator{}(props.lDirectHF);
+            Eax2SourceRoomValidator{}(props.lRoom);
+            Eax2SourceRoomHfValidator{}(props.lRoomHF);
+            Eax2SourceObstructionValidator{}(props.lObstruction);
+            Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio);
+            Eax2SourceOcclusionValidator{}(props.lOcclusion);
+            Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
+            Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
+            Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio);
+            Eax3SourceExclusionValidator{}(props.lExclusion);
+            Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio);
+            Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF);
+            Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor);
+            Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor);
+            Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor);
+            Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor);
+            Eax2SourceFlagsValidator{}(props.ulFlags);
+        }
+    };
 
-    void eax_defer_source_doppler_factor(
-        const EaxEaxCall& eax_call);
+    struct Eax5SourceAllValidator {
+        void operator()(const EAX50SOURCEPROPERTIES& props) const
+        {
+            Eax3SourceAllValidator{}(static_cast<const Eax3Props&>(props));
+            Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor);
+        }
+    };
 
-    void eax_defer_source_rolloff_factor(
-        const EaxEaxCall& eax_call);
+    struct Eax5SourceAll2dValidator {
+        void operator()(const EAXSOURCE2DPROPERTIES& props) const
+        {
+            Eax2SourceDirectValidator{}(props.lDirect);
+            Eax2SourceDirectHfValidator{}(props.lDirectHF);
+            Eax2SourceRoomValidator{}(props.lRoom);
+            Eax2SourceRoomHfValidator{}(props.lRoomHF);
+            Eax5SourceFlagsValidator{}(props.ulFlags);
+        }
+    };
 
-    void eax_defer_source_room_rolloff_factor(
-        const EaxEaxCall& eax_call);
+    struct Eax4ObstructionValidator {
+        void operator()(const EAXOBSTRUCTIONPROPERTIES& props) const
+        {
+            Eax2SourceObstructionValidator{}(props.lObstruction);
+            Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio);
+        }
+    };
 
-    void eax_defer_source_air_absorption_factor(
-        const EaxEaxCall& eax_call);
+    struct Eax4OcclusionValidator {
+        void operator()(const EAXOCCLUSIONPROPERTIES& props) const
+        {
+            Eax2SourceOcclusionValidator{}(props.lOcclusion);
+            Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
+            Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
+            Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio);
+        }
+    };
 
-    void eax_defer_source_flags(
-        const EaxEaxCall& eax_call);
+    struct Eax4ExclusionValidator {
+        void operator()(const EAXEXCLUSIONPROPERTIES& props) const
+        {
+            Eax3SourceExclusionValidator{}(props.lExclusion);
+            Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio);
+        }
+    };
 
-    void eax_defer_source_macro_fx_factor(
-        const EaxEaxCall& eax_call);
+    // Source validators
+    // ----------------------------------------------------------------------
+    // Send validators
 
-    void eax_defer_source_2d_all(
-        const EaxEaxCall& eax_call);
+    struct Eax4SendReceivingFxSlotIdValidator {
+        void operator()(const GUID& guidReceivingFXSlotID) const
+        {
+            if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3)
+            {
+                eax_fail_unknown_receiving_fx_slot_id();
+            }
+        }
+    };
 
-    void eax_defer_source_obstruction_all(
-        const EaxEaxCall& eax_call);
+    struct Eax5SendReceivingFxSlotIdValidator {
+        void operator()(const GUID& guidReceivingFXSlotID) const
+        {
+            if (guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 &&
+                guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3)
+            {
+                eax_fail_unknown_receiving_fx_slot_id();
+            }
+        }
+    };
 
-    void eax_defer_source_exclusion_all(
-        const EaxEaxCall& eax_call);
+    struct Eax4SendSendValidator {
+        void operator()(long lSend) const
+        {
+            eax_validate_range<Exception>(
+                "Send",
+                lSend,
+                EAXSOURCE_MINSEND,
+                EAXSOURCE_MAXSEND);
+        }
+    };
 
-    void eax_defer_source_occlusion_all(
-        const EaxEaxCall& eax_call);
+    struct Eax4SendSendHfValidator {
+        void operator()(long lSendHF) const
+        {
+            eax_validate_range<Exception>(
+                "Send HF",
+                lSendHF,
+                EAXSOURCE_MINSENDHF,
+                EAXSOURCE_MAXSENDHF);
+        }
+    };
 
-    void eax_defer_source_all(
-        const EaxEaxCall& eax_call);
+    template<typename TIdValidator>
+    struct EaxSendValidator {
+        void operator()(const EAXSOURCESENDPROPERTIES& props) const
+        {
+            TIdValidator{}(props.guidReceivingFXSlotID);
+            Eax4SendSendValidator{}(props.lSend);
+            Eax4SendSendHfValidator{}(props.lSendHF);
+        }
+    };
 
-    void eax_defer_source_speaker_level_all(
-        const EaxEaxCall& eax_call);
+    struct Eax4SendValidator : EaxSendValidator<Eax4SendReceivingFxSlotIdValidator> {};
+    struct Eax5SendValidator : EaxSendValidator<Eax5SendReceivingFxSlotIdValidator> {};
 
+    template<typename TIdValidator>
+    struct EaxOcclusionSendValidator {
+        void operator()(const EAXSOURCEOCCLUSIONSENDPROPERTIES& props) const
+        {
+            TIdValidator{}(props.guidReceivingFXSlotID);
+            Eax2SourceOcclusionValidator{}(props.lOcclusion);
+            Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
+            Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
+            Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio);
+        }
+    };
 
-    void eax_set_outside_volume_hf();
+    struct Eax4OcclusionSendValidator : EaxOcclusionSendValidator<Eax4SendReceivingFxSlotIdValidator> {};
+    struct Eax5OcclusionSendValidator : EaxOcclusionSendValidator<Eax5SendReceivingFxSlotIdValidator> {};
 
-    void eax_set_doppler_factor();
+    template<typename TIdValidator>
+    struct EaxExclusionSendValidator {
+        void operator()(const EAXSOURCEEXCLUSIONSENDPROPERTIES& props) const
+        {
+            TIdValidator{}(props.guidReceivingFXSlotID);
+            Eax3SourceExclusionValidator{}(props.lExclusion);
+            Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio);
+        }
+    };
 
-    void eax_set_rolloff_factor();
+    struct Eax4ExclusionSendValidator : EaxExclusionSendValidator<Eax4SendReceivingFxSlotIdValidator> {};
+    struct Eax5ExclusionSendValidator : EaxExclusionSendValidator<Eax5SendReceivingFxSlotIdValidator> {};
 
-    void eax_set_room_rolloff_factor();
+    template<typename TIdValidator>
+    struct EaxAllSendValidator {
+        void operator()(const EAXSOURCEALLSENDPROPERTIES& props) const
+        {
+            TIdValidator{}(props.guidReceivingFXSlotID);
+            Eax4SendSendValidator{}(props.lSend);
+            Eax4SendSendHfValidator{}(props.lSendHF);
+            Eax2SourceOcclusionValidator{}(props.lOcclusion);
+            Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio);
+            Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio);
+            Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio);
+            Eax3SourceExclusionValidator{}(props.lExclusion);
+            Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio);
+        }
+    };
 
-    void eax_set_air_absorption_factor();
+    struct Eax4AllSendValidator : EaxAllSendValidator<Eax4SendReceivingFxSlotIdValidator> {};
+    struct Eax5AllSendValidator : EaxAllSendValidator<Eax5SendReceivingFxSlotIdValidator> {};
 
+    // Send validators
+    // ----------------------------------------------------------------------
+    // Active FX slot ID validators
 
-    void eax_set_direct_hf_auto_flag();
+    struct Eax4ActiveFxSlotIdValidator {
+        void operator()(const GUID &guid) const
+        {
+            if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID
+                && guid != EAXPROPERTYID_EAX40_FXSlot0 && guid != EAXPROPERTYID_EAX40_FXSlot1
+                && guid != EAXPROPERTYID_EAX40_FXSlot2 && guid != EAXPROPERTYID_EAX40_FXSlot3)
+            {
+                eax_fail_unknown_active_fx_slot_id();
+            }
+        }
+    };
 
-    void eax_set_room_auto_flag();
+    struct Eax5ActiveFxSlotIdValidator {
+        void operator()(const GUID &guid) const
+        {
+            if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID
+                && guid != EAXPROPERTYID_EAX50_FXSlot0 && guid != EAXPROPERTYID_EAX50_FXSlot1
+                && guid != EAXPROPERTYID_EAX50_FXSlot2 && guid != EAXPROPERTYID_EAX50_FXSlot3)
+            {
+                eax_fail_unknown_active_fx_slot_id();
+            }
+        }
+    };
 
-    void eax_set_room_hf_auto_flag();
+    // Active FX slot ID validators
+    // ----------------------------------------------------------------------
+    // Speaker level validators.
 
-    void eax_set_flags();
+    struct Eax5SpeakerIdValidator {
+        void operator()(long lSpeakerID) const
+        {
+            switch (lSpeakerID) {
+                case EAXSPEAKER_FRONT_LEFT:
+                case EAXSPEAKER_FRONT_CENTER:
+                case EAXSPEAKER_FRONT_RIGHT:
+                case EAXSPEAKER_SIDE_RIGHT:
+                case EAXSPEAKER_REAR_RIGHT:
+                case EAXSPEAKER_REAR_CENTER:
+                case EAXSPEAKER_REAR_LEFT:
+                case EAXSPEAKER_SIDE_LEFT:
+                case EAXSPEAKER_LOW_FREQUENCY:
+                    break;
+
+                default:
+                    eax_fail("Unknown speaker ID.");
+            }
+        }
+    };
 
+    struct Eax5SpeakerLevelValidator {
+        void operator()(long lLevel) const
+        {
+            // TODO Use a range when the feature will be implemented.
+            if (lLevel != EAXSOURCE_DEFAULTSPEAKERLEVEL)
+                eax_fail("Speaker level out of range.");
+        }
+    };
 
-    void eax_set_macro_fx_factor();
+    struct Eax5SpeakerAllValidator {
+        void operator()(const EAXSPEAKERLEVELPROPERTIES& all) const
+        {
+            Eax5SpeakerIdValidator{}(all.lSpeakerID);
+            Eax5SpeakerLevelValidator{}(all.lLevel);
+        }
+    };
 
-    void eax_set_speaker_levels();
+    // Speaker level validators.
+    // ----------------------------------------------------------------------
 
+    struct Eax4SendIndexGetter {
+        EaxFxSlotIndexValue operator()(const GUID &guid) const
+        {
+            if(guid == EAXPROPERTYID_EAX40_FXSlot0)
+                return 0;
+            if(guid == EAXPROPERTYID_EAX40_FXSlot1)
+                return 1;
+            if(guid == EAXPROPERTYID_EAX40_FXSlot2)
+                return 2;
+            if(guid == EAXPROPERTYID_EAX40_FXSlot3)
+                return 3;
+            eax_fail_unknown_receiving_fx_slot_id();
+        }
+    };
 
-    void eax1_set_efx();
-    void eax1_set_reverb_mix(const EaxEaxCall& eax_call);
-    void eax1_set(const EaxEaxCall& eax_call);
+    struct Eax5SendIndexGetter {
+        EaxFxSlotIndexValue operator()(const GUID &guid) const
+        {
+            if(guid == EAXPROPERTYID_EAX50_FXSlot0)
+                return 0;
+            if(guid == EAXPROPERTYID_EAX50_FXSlot1)
+                return 1;
+            if(guid == EAXPROPERTYID_EAX50_FXSlot2)
+                return 2;
+            if(guid == EAXPROPERTYID_EAX50_FXSlot3)
+                return 3;
+            eax_fail_unknown_receiving_fx_slot_id();
+        }
+    };
 
-    void eax_apply_deferred();
+    [[noreturn]] static void eax_fail(const char* message);
+    [[noreturn]] static void eax_fail_unknown_property_id();
+    [[noreturn]] static void eax_fail_unknown_version();
+    [[noreturn]] static void eax_fail_unknown_active_fx_slot_id();
+    [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id();
+
+    static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept;
+    static void eax1_set_defaults(Eax1Props& props) noexcept;
+    void eax1_set_defaults() noexcept;
+    static void eax2_set_defaults(Eax2Props& props) noexcept;
+    void eax2_set_defaults() noexcept;
+    static void eax3_set_defaults(Eax3Props& props) noexcept;
+    void eax3_set_defaults() noexcept;
+    static void eax4_set_sends_defaults(EaxSends& sends) noexcept;
+    static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept;
+    void eax4_set_defaults() noexcept;
+    static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept;
+    static void eax5_set_sends_defaults(EaxSends& sends) noexcept;
+    static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept;
+    static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept;
+    static void eax5_set_defaults(Eax5Props& props) noexcept;
+    void eax5_set_defaults() noexcept;
+    void eax_set_defaults() noexcept;
 
-    void eax_set(
-        const EaxEaxCall& eax_call);
+    static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept;
+    static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept;
+    static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept;
+    static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept;
 
+    static float eax_calculate_dst_occlusion_mb(
+        long src_occlusion_mb,
+        float path_ratio,
+        float lf_ratio) noexcept;
 
-    static const GUID& eax_get_send_fx_slot_guid(
-        int eax_version,
-        EaxFxSlotIndexValue fx_slot_index);
+    [[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam;
 
-    static void eax_copy_send(
-        const EAXSOURCEALLSENDPROPERTIES& src_send,
-        EAXSOURCESENDPROPERTIES& dst_send);
+    [[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot,
+        const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam;
 
-    static void eax_copy_send(
-        const EAXSOURCEALLSENDPROPERTIES& src_send,
-        EAXSOURCEALLSENDPROPERTIES& dst_send);
+    void eax_update_direct_filter();
+    void eax_update_room_filters();
+    void eax_commit_filters();
 
-    static void eax_copy_send(
-        const EAXSOURCEALLSENDPROPERTIES& src_send,
-        EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send);
+    static void eax_copy_send_for_get(
+        const EAXSOURCEALLSENDPROPERTIES& src,
+        EAXSOURCESENDPROPERTIES& dst) noexcept
+    {
+        dst = reinterpret_cast<const EAXSOURCESENDPROPERTIES&>(src);
+    }
 
-    static void eax_copy_send(
-        const EAXSOURCEALLSENDPROPERTIES& src_send,
-        EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send);
+    static void eax_copy_send_for_get(
+        const EAXSOURCEALLSENDPROPERTIES& src,
+        EAXSOURCEALLSENDPROPERTIES& dst) noexcept
+    {
+        dst = src;
+    }
 
-    template<
-        typename TException,
-        typename TSrcSend
-    >
-    void eax_api_get_send_properties(
-        const EaxEaxCall& eax_call) const
+    static void eax_copy_send_for_get(
+        const EAXSOURCEALLSENDPROPERTIES& src,
+        EAXSOURCEOCCLUSIONSENDPROPERTIES& dst) noexcept
     {
-        const auto eax_version = eax_call.get_version();
-        const auto dst_sends = eax_call.get_values<TException, TSrcSend>();
-        const auto send_count = dst_sends.size();
+        dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID;
+        dst.lOcclusion = src.lOcclusion;
+        dst.flOcclusionLFRatio = src.flOcclusionLFRatio;
+        dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
+        dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio;
+    }
 
-        for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index)
-        {
-            auto& dst_send = dst_sends[fx_slot_index];
-            const auto& src_send = eax_.sends[fx_slot_index];
+    static void eax_copy_send_for_get(
+        const EAXSOURCEALLSENDPROPERTIES& src,
+        EAXSOURCEEXCLUSIONSENDPROPERTIES& dst) noexcept
+    {
+        dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID;
+        dst.lExclusion = src.lExclusion;
+        dst.flExclusionLFRatio = src.flExclusionLFRatio;
+    }
 
-            eax_copy_send(src_send, dst_send);
+    template<typename TDstSend>
+    void eax_get_sends(const EaxCall& call, const EaxSends& src_sends)
+    {
+        const auto dst_sends = call.get_values<TDstSend>(EAX_MAX_FXSLOTS);
+        const auto count = dst_sends.size();
 
-            dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index);
+        for (auto i = decltype(count){}; i < count; ++i) {
+            const auto& src_send = src_sends[i];
+            auto& dst_send = dst_sends[i];
+            eax_copy_send_for_get(src_send, dst_send);
         }
     }
 
+    static void eax_get_active_fx_slot_id(const EaxCall& call, const 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);
+    void eax4_get(const EaxCall& call, const Eax4Props& props);
+    static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props);
+    static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props);
+    void eax5_get(const EaxCall& call, const Eax5Props& props);
+    void eax_get(const EaxCall& call);
+
+    static void eax_copy_send_for_set(
+        const EAXSOURCESENDPROPERTIES& src,
+        EAXSOURCEALLSENDPROPERTIES& dst) noexcept
+    {
+        dst.lSend = src.lSend;
+        dst.lSendHF = src.lSendHF;
+    }
 
-    void eax1_get(const EaxEaxCall& eax_call);
-
-    void eax_api_get_source_all_v2(
-        const EaxEaxCall& eax_call);
+    static void eax_copy_send_for_set(
+        const EAXSOURCEALLSENDPROPERTIES& src,
+        EAXSOURCEALLSENDPROPERTIES& dst) noexcept
+    {
+        dst.lSend = src.lSend;
+        dst.lSendHF = src.lSendHF;
+        dst.lOcclusion = src.lOcclusion;
+        dst.flOcclusionLFRatio = src.flOcclusionLFRatio;
+        dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
+        dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio;
+        dst.lExclusion = src.lExclusion;
+        dst.flExclusionLFRatio = src.flExclusionLFRatio;
+    }
 
-    void eax_api_get_source_all_v3(
-        const EaxEaxCall& eax_call);
+    static void eax_copy_send_for_set(
+        const EAXSOURCEOCCLUSIONSENDPROPERTIES& src,
+        EAXSOURCEALLSENDPROPERTIES& dst) noexcept
+    {
+        dst.lOcclusion = src.lOcclusion;
+        dst.flOcclusionLFRatio = src.flOcclusionLFRatio;
+        dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio;
+        dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio;
+    }
 
-    void eax_api_get_source_all_v5(
-        const EaxEaxCall& eax_call);
+    static void eax_copy_send_for_set(
+        const EAXSOURCEEXCLUSIONSENDPROPERTIES& src,
+        EAXSOURCEALLSENDPROPERTIES& dst) noexcept
+    {
+        dst.lExclusion = src.lExclusion;
+        dst.flExclusionLFRatio = src.flExclusionLFRatio;
+    }
 
-    void eax_api_get_source_all(
-        const EaxEaxCall& eax_call);
+    template<typename TValidator, typename TIndexGetter, typename TSrcSend>
+    void eax_defer_sends(const EaxCall& call, EaxSends& dst_sends)
+    {
+        const auto src_sends = call.get_values<const TSrcSend>(EAX_MAX_FXSLOTS);
+        std::for_each(src_sends.cbegin(), src_sends.cend(), TValidator{});
+        const auto count = src_sends.size();
+        const auto index_getter = TIndexGetter{};
+
+        for (auto i = decltype(count){}; i < count; ++i) {
+            const auto& src_send = src_sends[i];
+            const auto dst_index = index_getter(src_send.guidReceivingFXSlotID);
+            auto& dst_send = dst_sends[dst_index];
+            eax_copy_send_for_set(src_send, dst_send);
+        }
+    }
 
-    void eax_api_get_source_all_obstruction(
-        const EaxEaxCall& eax_call);
+    template<typename TValidator, typename TSrcSend>
+    void eax4_defer_sends(const EaxCall& call, EaxSends& dst_sends)
+    {
+        eax_defer_sends<TValidator, Eax4SendIndexGetter, TSrcSend>(call, dst_sends);
+    }
 
-    void eax_api_get_source_all_occlusion(
-        const EaxEaxCall& eax_call);
+    template<typename TValidator, typename TSrcSend>
+    void eax5_defer_sends(const EaxCall& call, EaxSends& dst_sends)
+    {
+        eax_defer_sends<TValidator, Eax5SendIndexGetter, TSrcSend>(call, dst_sends);
+    }
 
-    void eax_api_get_source_all_exclusion(
-        const EaxEaxCall& eax_call);
+    template<typename TValidator, size_t TIdCount>
+    void eax_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
+    {
+        const auto src_ids = call.get_values<const GUID>(TIdCount);
+        std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{});
+        std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids.begin());
+    }
 
-    void eax_api_get_source_active_fx_slot_id(
-        const EaxEaxCall& eax_call);
+    template<size_t TIdCount>
+    void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
+    {
+        eax_defer_active_fx_slot_id<Eax4ActiveFxSlotIdValidator>(call, dst_ids);
+    }
 
-    void eax_api_get_source_all_2d(
-        const EaxEaxCall& eax_call);
+    template<size_t TIdCount>
+    void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span<GUID,TIdCount> dst_ids)
+    {
+        eax_defer_active_fx_slot_id<Eax5ActiveFxSlotIdValidator>(call, dst_ids);
+    }
 
-    void eax_api_get_source_speaker_level_all(
-        const EaxEaxCall& eax_call);
+    template<typename TValidator, typename TProperty>
+    static void eax_defer(const EaxCall& call, TProperty& property)
+    {
+        const auto& value = call.get_value<Exception, const TProperty>();
+        TValidator{}(value);
+        property = value;
+    }
 
-    void eax_get(
-        const EaxEaxCall& eax_call);
+    // Defers source's sub-properties (obstruction, occlusion, exclusion).
+    template<typename TValidator, typename TSubproperty, typename TProperty>
+    void eax_defer_sub(const EaxCall& call, TProperty& property)
+    {
+        const auto& src_props = call.get_value<Exception, const TSubproperty>();
+        TValidator{}(src_props);
+        auto& dst_props = reinterpret_cast<TSubproperty&>(property);
+        dst_props = src_props;
+    }
 
+    void eax_set_efx_outer_gain_hf();
+    void eax_set_efx_doppler_factor();
+    void eax_set_efx_rolloff_factor();
+    void eax_set_efx_room_rolloff_factor();
+    void eax_set_efx_air_absorption_factor();
+    void eax_set_efx_dry_gain_hf_auto();
+    void eax_set_efx_wet_gain_auto();
+    void eax_set_efx_wet_gain_hf_auto();
+
+    static void eax1_set(const EaxCall& call, Eax1Props& props);
+    static void eax2_set(const EaxCall& call, Eax2Props& props);
+    void eax3_set(const EaxCall& call, Eax3Props& props);
+    void eax4_set(const EaxCall& call, Eax4Props& props);
+    static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props);
+    static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props);
+    void eax5_set(const EaxCall& call, Eax5Props& props);
+    void eax_set(const EaxCall& call);
 
     // `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)`
     void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx,
         const EaxAlLowPassParam &filter);
+
+    void eax_commit_active_fx_slots();
 #endif // ALSOFT_EAX
 };
 
 void UpdateAllSourceProps(ALCcontext *context);
 
+struct SourceSubList {
+    uint64_t FreeMask{~0_u64};
+    gsl::owner<std::array<ALsource,64>*> Sources{nullptr};
+
+    SourceSubList() noexcept = default;
+    SourceSubList(const SourceSubList&) = delete;
+    SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
+    { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
+    ~SourceSubList();
+
+    SourceSubList& operator=(const SourceSubList&) = delete;
+    SourceSubList& operator=(SourceSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
+};
+
 #endif

文件差异内容过多而无法显示
+ 333 - 672
libs/openal-soft/al/state.cpp


文件差异内容过多而无法显示
+ 109 - 747
libs/openal-soft/alc/alc.cpp


+ 207 - 198
libs/openal-soft/alc/alconfig.cpp

@@ -22,9 +22,6 @@
 
 #include "alconfig.h"
 
-#include <cstdlib>
-#include <cctype>
-#include <cstring>
 #ifdef _WIN32
 #include <windows.h>
 #include <shlobj.h>
@@ -34,25 +31,47 @@
 #endif
 
 #include <algorithm>
-#include <cstdio>
+#include <array>
+#include <cctype>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <istream>
+#include <limits>
 #include <string>
+#include <string_view>
 #include <utility>
+#include <vector>
 
-#include "alfstream.h"
+#include "almalloc.h"
 #include "alstring.h"
 #include "core/helpers.h"
 #include "core/logging.h"
 #include "strutils.h"
-#include "vector.h"
 
+#if defined(ALSOFT_UWP)
+#include <winrt/Windows.Media.Core.h> // !!This is important!!
+#include <winrt/Windows.Storage.h>
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Foundation.Collections.h>
+using namespace winrt;
+#endif
 
 namespace {
 
+using namespace std::string_view_literals;
+
+#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
+struct CoTaskMemDeleter {
+    void operator()(void *mem) const { CoTaskMemFree(mem); }
+};
+#endif
+
 struct ConfigEntry {
     std::string key;
     std::string value;
 };
-al::vector<ConfigEntry> ConfOpts;
+std::vector<ConfigEntry> ConfOpts;
 
 
 std::string &lstrip(std::string &line)
@@ -72,57 +91,48 @@ bool readline(std::istream &f, std::string &output)
     return std::getline(f, output) && !output.empty();
 }
 
-std::string expdup(const char *str)
+std::string expdup(std::string_view str)
 {
     std::string output;
 
-    std::string envval;
-    while(*str != '\0')
+    while(!str.empty())
     {
-        const char *addstr;
-        size_t addstrlen;
-
-        if(str[0] != '$')
+        if(auto nextpos = str.find('$'))
         {
-            const char *next = std::strchr(str, '$');
-            addstr = str;
-            addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
+            output += str.substr(0, nextpos);
+            if(nextpos == std::string_view::npos)
+                break;
 
-            str += addstrlen;
+            str.remove_prefix(nextpos);
         }
-        else
-        {
-            str++;
-            if(*str == '$')
-            {
-                const char *next = std::strchr(str+1, '$');
-                addstr = str;
-                addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
 
-                str += addstrlen;
-            }
-            else
-            {
-                const bool hasbraces{(*str == '{')};
+        str.remove_prefix(1);
+        if(str.empty())
+        {
+            output += '$';
+            break;
+        }
+        if(str.front() == '$')
+        {
+            output += '$';
+            str.remove_prefix(1);
+            continue;
+        }
 
-                if(hasbraces) str++;
-                const char *envstart = str;
-                while(std::isalnum(*str) || *str == '_')
-                    ++str;
-                if(hasbraces && *str != '}')
-                    continue;
-                const std::string envname{envstart, str};
-                if(hasbraces) str++;
+        const bool hasbraces{str.front() == '{'};
+        if(hasbraces) str.remove_prefix(1);
 
-                envval = al::getenv(envname.c_str()).value_or(std::string{});
-                addstr = envval.data();
-                addstrlen = envval.length();
-            }
-        }
-        if(addstrlen == 0)
+        size_t envend{0};
+        while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_'))
+            ++envend;
+        if(hasbraces && (envend == str.size() || str[envend] != '}'))
             continue;
+        const std::string envname{str.substr(0, envend)};
+        if(hasbraces) ++envend;
+        str.remove_prefix(envend);
 
-        output.append(addstr, addstrlen);
+        if(auto envval = al::getenv(envname.c_str()))
+            output += *envval;
     }
 
     return output;
@@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f)
 
         if(buffer[0] == '[')
         {
-            char *line{&buffer[0]};
-            char *section = line+1;
-            char *endsection;
-
-            endsection = std::strchr(section, ']');
-            if(!endsection || section == endsection)
+            auto endpos = buffer.find(']', 1);
+            if(endpos == 1 || endpos == std::string::npos)
             {
-                ERR(" config parse error: bad line \"%s\"\n", line);
+                ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
                 continue;
             }
-            if(endsection[1] != 0)
+            if(buffer[endpos+1] != '\0')
             {
-                char *end = endsection+1;
-                while(std::isspace(*end))
-                    ++end;
-                if(*end != 0 && *end != '#')
+                size_t last{endpos+1};
+                while(last < buffer.size() && std::isspace(buffer[last]))
+                    ++last;
+
+                if(last < buffer.size() && buffer[last] != '#')
                 {
-                    ERR(" config parse error: bad line \"%s\"\n", line);
+                    ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
                     continue;
                 }
             }
-            *endsection = 0;
+
+            auto section = std::string_view{buffer}.substr(1, endpos-1);
 
             curSection.clear();
-            if(al::strcasecmp(section, "general") != 0)
+            if(al::case_compare(section, "general"sv) != 0)
             {
                 do {
-                    char *nextp = std::strchr(section, '%');
-                    if(!nextp)
+                    auto nextp = section.find('%');
+                    if(nextp == std::string_view::npos)
                     {
                         curSection += section;
                         break;
                     }
 
-                    curSection.append(section, nextp);
-                    section = nextp;
+                    curSection += section.substr(0, nextp);
+                    section.remove_prefix(nextp);
 
-                    if(((section[1] >= '0' && section[1] <= '9') ||
+                    if(section.size() > 2 &&
+                       ((section[1] >= '0' && section[1] <= '9') ||
                         (section[1] >= 'a' && section[1] <= 'f') ||
                         (section[1] >= 'A' && section[1] <= 'F')) &&
                        ((section[2] >= '0' && section[2] <= '9') ||
@@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f)
                         else if(section[2] >= 'A' && section[2] <= 'F')
                             b |= (section[2]-'A'+0x0a);
                         curSection += static_cast<char>(b);
-                        section += 3;
+                        section.remove_prefix(3);
                     }
-                    else if(section[1] == '%')
+                    else if(section.size() > 1 && section[1] == '%')
                     {
                         curSection += '%';
-                        section += 2;
+                        section.remove_prefix(2);
                     }
                     else
                     {
                         curSection += '%';
-                        section += 1;
+                        section.remove_prefix(1);
                     }
-                } while(*section != 0);
+                } while(!section.empty());
             }
 
             continue;
@@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f)
             ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
             continue;
         }
-        auto keyend = sep++;
-        while(keyend > 0 && std::isspace(buffer[keyend-1]))
-            --keyend;
-        if(!keyend)
+        auto keypart = std::string_view{buffer}.substr(0, sep++);
+        while(!keypart.empty() && std::isspace(keypart.back()))
+            keypart.remove_suffix(1);
+        if(keypart.empty())
         {
             ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
             continue;
         }
-        while(sep < buffer.size() && std::isspace(buffer[sep]))
-            sep++;
+        auto valpart = std::string_view{buffer}.substr(sep);
+        while(!valpart.empty() && std::isspace(valpart.front()))
+            valpart.remove_prefix(1);
 
         std::string fullKey;
         if(!curSection.empty())
@@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f)
             fullKey += curSection;
             fullKey += '/';
         }
-        fullKey += buffer.substr(0u, keyend);
+        fullKey += keypart;
 
-        std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
-        if(value.size() > 1)
+        if(valpart.size() > std::numeric_limits<int>::max())
+        {
+            ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
+            continue;
+        }
+        if(valpart.size() > 1)
         {
-            if((value.front() == '"' && value.back() == '"')
-                || (value.front() == '\'' && value.back() == '\''))
+            if((valpart.front() == '"' && valpart.back() == '"')
+                || (valpart.front() == '\'' && valpart.back() == '\''))
             {
-                value.pop_back();
-                value.erase(value.begin());
+                valpart.remove_prefix(1);
+                valpart.remove_suffix(1);
             }
         }
 
-        TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
+        TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data());
 
         /* Check if we already have this option set */
         auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
@@ -266,61 +280,49 @@ void LoadConfigFromFile(std::istream &f)
         auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
         if(ent != ConfOpts.end())
         {
-            if(!value.empty())
-                ent->value = expdup(value.c_str());
+            if(!valpart.empty())
+                ent->value = expdup(valpart);
             else
                 ConfOpts.erase(ent);
         }
-        else if(!value.empty())
-            ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
+        else if(!valpart.empty())
+            ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)});
     }
     ConfOpts.shrink_to_fit();
 }
 
-const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
+const char *GetConfigValue(const std::string_view devName, const std::string_view blockName,
+    const std::string_view keyName)
 {
-    if(!keyName)
+    if(keyName.empty())
         return nullptr;
 
     std::string key;
-    if(blockName && al::strcasecmp(blockName, "general") != 0)
+    if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
     {
         key = blockName;
-        if(devName)
-        {
-            key += '/';
-            key += devName;
-        }
         key += '/';
-        key += keyName;
     }
-    else
+    if(!devName.empty())
     {
-        if(devName)
-        {
-            key = devName;
-            key += '/';
-        }
-        key += keyName;
+        key += devName;
+        key += '/';
     }
+    key += keyName;
 
     auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
-        [&key](const ConfigEntry &entry) -> bool
-        { return entry.key == key; });
+        [&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
     if(iter != ConfOpts.cend())
     {
-        TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
+        TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str());
         if(!iter->value.empty())
             return iter->value.c_str();
         return nullptr;
     }
 
-    if(!devName)
-    {
-        TRACE("Key %s not found\n", key.c_str());
+    if(devName.empty())
         return nullptr;
-    }
-    return GetConfigValue(nullptr, blockName, keyName);
+    return GetConfigValue({}, blockName, keyName);
 }
 
 } // namespace
@@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha
 #ifdef _WIN32
 void ReadALConfig()
 {
-    WCHAR buffer[MAX_PATH];
-    if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
+    namespace fs = std::filesystem;
+    fs::path path;
+
+#if !defined(_GAMING_XBOX)
     {
-        std::string filepath{wstr_to_utf8(buffer)};
-        filepath += "\\alsoft.ini";
+#if !defined(ALSOFT_UWP)
+        std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
+        const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
+            nullptr, al::out_ptr(bufstore))};
+        if(SUCCEEDED(hr))
+        {
+            const std::wstring_view buffer{bufstore.get()};
+#else
+        winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
+        auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
+        std::wstring_view buffer{bufstore};
+        {
+#endif
+            path = fs::path{buffer};
+            path /= L"alsoft.ini";
 
-        TRACE("Loading config %s...\n", filepath.c_str());
-        al::ifstream f{filepath};
-        if(f.is_open())
-            LoadConfigFromFile(f);
+            TRACE("Loading config %s...\n", path.u8string().c_str());
+            if(std::ifstream f{path}; f.is_open())
+                LoadConfigFromFile(f);
+        }
     }
+#endif
 
-    std::string ppath{GetProcBinary().path};
-    if(!ppath.empty())
+    path = fs::u8path(GetProcBinary().path);
+    if(!path.empty())
     {
-        ppath += "\\alsoft.ini";
-        TRACE("Loading config %s...\n", ppath.c_str());
-        al::ifstream f{ppath};
-        if(f.is_open())
+        path /= "alsoft.ini";
+        TRACE("Loading config %s...\n", path.u8string().c_str());
+        if(std::ifstream f{path}; f.is_open())
             LoadConfigFromFile(f);
     }
 
     if(auto confpath = al::getenv(L"ALSOFT_CONF"))
     {
-        TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
-        al::ifstream f{*confpath};
-        if(f.is_open())
+        path = *confpath;
+        TRACE("Loading config %s...\n", path.u8string().c_str());
+        if(std::ifstream f{path}; f.is_open())
             LoadConfigFromFile(f);
     }
 }
@@ -364,13 +381,12 @@ void ReadALConfig()
 
 void ReadALConfig()
 {
-    const char *str{"/etc/openal/alsoft.conf"};
+    namespace fs = std::filesystem;
+    fs::path path{"/etc/openal/alsoft.conf"};
 
-    TRACE("Loading config %s...\n", str);
-    al::ifstream f{str};
-    if(f.is_open())
+    TRACE("Loading config %s...\n", path.u8string().c_str());
+    if(std::ifstream f{path}; f.is_open())
         LoadConfigFromFile(f);
-    f.close();
 
     std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
     /* Go through the list in reverse, since "the order of base directories
@@ -378,48 +394,43 @@ void ReadALConfig()
      * important". Ergo, we need to load the settings from the later dirs
      * first so that the settings in the earlier dirs override them.
      */
-    std::string fname;
     while(!confpaths.empty())
     {
-        auto next = confpaths.find_last_of(':');
+        auto next = confpaths.rfind(':');
         if(next < confpaths.length())
         {
-            fname = confpaths.substr(next+1);
+            path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal();
             confpaths.erase(next);
         }
         else
         {
-            fname = confpaths;
+            path = fs::path{confpaths}.lexically_normal();
             confpaths.clear();
         }
 
-        if(fname.empty() || fname.front() != '/')
-            WARN("Ignoring XDG config dir: %s\n", fname.c_str());
+        if(!path.is_absolute())
+            WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
         else
         {
-            if(fname.back() != '/') fname += "/alsoft.conf";
-            else fname += "alsoft.conf";
+            path /= "alsoft.conf";
 
-            TRACE("Loading config %s...\n", fname.c_str());
-            f = al::ifstream{fname};
-            if(f.is_open())
+            TRACE("Loading config %s...\n", path.u8string().c_str());
+            if(std::ifstream f{path}; f.is_open())
                 LoadConfigFromFile(f);
         }
-        fname.clear();
     }
 
 #ifdef __APPLE__
     CFBundleRef mainBundle = CFBundleGetMainBundle();
     if(mainBundle)
     {
-        unsigned char fileName[PATH_MAX];
-        CFURLRef configURL;
+        CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
+            nullptr)};
 
-        if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
-           CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
+        std::array<unsigned char,PATH_MAX> fileName{};
+        if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
         {
-            f = al::ifstream{reinterpret_cast<char*>(fileName)};
-            if(f.is_open())
+            if(std::ifstream f{reinterpret_cast<char*>(fileName.data())}; f.is_open())
                 LoadConfigFromFile(f);
         }
     }
@@ -427,102 +438,100 @@ void ReadALConfig()
 
     if(auto homedir = al::getenv("HOME"))
     {
-        fname = *homedir;
-        if(fname.back() != '/') fname += "/.alsoftrc";
-        else fname += ".alsoftrc";
+        path = *homedir;
+        path /= ".alsoftrc";
 
-        TRACE("Loading config %s...\n", fname.c_str());
-        f = al::ifstream{fname};
-        if(f.is_open())
+        TRACE("Loading config %s...\n", path.u8string().c_str());
+        if(std::ifstream f{path}; f.is_open())
             LoadConfigFromFile(f);
     }
 
     if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
     {
-        fname = *configdir;
-        if(fname.back() != '/') fname += "/alsoft.conf";
-        else fname += "alsoft.conf";
+        path = *configdir;
+        path /= "alsoft.conf";
     }
     else
     {
-        fname.clear();
+        path.clear();
         if(auto homedir = al::getenv("HOME"))
         {
-            fname = *homedir;
-            if(fname.back() != '/') fname += "/.config/alsoft.conf";
-            else fname += ".config/alsoft.conf";
+            path = *homedir;
+            path /= ".config/alsoft.conf";
         }
     }
-    if(!fname.empty())
+    if(!path.empty())
     {
-        TRACE("Loading config %s...\n", fname.c_str());
-        f = al::ifstream{fname};
-        if(f.is_open())
+        TRACE("Loading config %s...\n", path.u8string().c_str());
+        if(std::ifstream f{path}; f.is_open())
             LoadConfigFromFile(f);
     }
 
-    std::string ppath{GetProcBinary().path};
-    if(!ppath.empty())
+    path = GetProcBinary().path;
+    if(!path.empty())
     {
-        if(ppath.back() != '/') ppath += "/alsoft.conf";
-        else ppath += "alsoft.conf";
+        path /= "alsoft.conf";
 
-        TRACE("Loading config %s...\n", ppath.c_str());
-        f = al::ifstream{ppath};
-        if(f.is_open())
+        TRACE("Loading config %s...\n", path.u8string().c_str());
+        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());
-        f = al::ifstream{*confname};
-        if(f.is_open())
+        if(std::ifstream f{*confname}; f.is_open())
             LoadConfigFromFile(f);
     }
 }
 #endif
 
-al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
+std::optional<std::string> ConfigValueStr(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName)
 {
     if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return al::make_optional<std::string>(val);
-    return al::nullopt;
+        return val;
+    return std::nullopt;
 }
 
-al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
+std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
+    const std::string_view keyName)
 {
     if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
-    return al::nullopt;
+        return static_cast<int>(std::strtol(val, nullptr, 0));
+    return std::nullopt;
 }
 
-al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
+std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName)
 {
     if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
-    return al::nullopt;
+        return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
+    return std::nullopt;
 }
 
-al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
+std::optional<float> ConfigValueFloat(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName)
 {
     if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return al::make_optional(std::strtof(val, nullptr));
-    return al::nullopt;
+        return std::strtof(val, nullptr);
+    return std::nullopt;
 }
 
-al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
+std::optional<bool> ConfigValueBool(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName)
 {
     if(const char *val{GetConfigValue(devName, blockName, keyName)})
-        return al::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
-            || al::strcasecmp(val, "true")==0 || atoi(val) != 0);
-    return al::nullopt;
+        return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
+            || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
+    return std::nullopt;
 }
 
-bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
+bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
+    const std::string_view keyName, bool def)
 {
     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);
+        return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
+            || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
     return def;
 }

+ 14 - 7
libs/openal-soft/alc/alconfig.h

@@ -1,18 +1,25 @@
 #ifndef ALCONFIG_H
 #define ALCONFIG_H
 
+#include <optional>
 #include <string>
+#include <string_view>
 
-#include "aloptional.h"
 
 void ReadALConfig();
 
-bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def);
+bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
+    const std::string_view keyName, bool def);
 
-al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
-al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
-al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
-al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
-al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
+std::optional<std::string> ConfigValueStr(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName);
+std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
+    const std::string_view keyName);
+std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName);
+std::optional<float> ConfigValueFloat(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName);
+std::optional<bool> ConfigValueBool(const std::string_view devName,
+    const std::string_view blockName, const std::string_view keyName);
 
 #endif /* ALCONFIG_H */

文件差异内容过多而无法显示
+ 464 - 272
libs/openal-soft/alc/alu.cpp


+ 5 - 5
libs/openal-soft/alc/alu.h

@@ -2,14 +2,14 @@
 #define ALU_H
 
 #include <bitset>
-
-#include "aloptional.h"
+#include <optional>
+#include <stdint.h>
 
 struct ALCcontext;
 struct ALCdevice;
 struct EffectSlot;
 
-enum class StereoEncoding : unsigned char;
+enum class StereoEncoding : uint8_t;
 
 
 constexpr float GainMixMax{1000.0f}; /* +60dB */
@@ -24,14 +24,14 @@ enum CompatFlags : uint8_t {
 };
 using CompatFlagBitset = std::bitset<CompatFlags::Count>;
 
-void aluInit(CompatFlagBitset flags);
+void aluInit(CompatFlagBitset flags, const float nfcscale);
 
 /* aluInitRenderer
  *
  * Set up the appropriate panning method and mixing method given the device
  * properties.
  */
-void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode);
+void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
 
 void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
 

+ 180 - 145
libs/openal-soft/alc/backends/alsa.cpp

@@ -31,29 +31,33 @@
 #include <exception>
 #include <functional>
 #include <memory>
+#include <mutex>
 #include <string>
+#include <string_view>
 #include <thread>
 #include <utility>
+#include <vector>
 
-#include "albyte.h"
+#include "albit.h"
 #include "alc/alconfig.h"
 #include "almalloc.h"
 #include "alnumeric.h"
-#include "aloptional.h"
+#include "alstring.h"
+#include "althrd_setname.h"
 #include "core/device.h"
 #include "core/helpers.h"
 #include "core/logging.h"
 #include "dynload.h"
 #include "ringbuffer.h"
-#include "threads.h"
-#include "vector.h"
 
 #include <alsa/asoundlib.h>
 
 
 namespace {
 
-constexpr char alsaDevice[] = "ALSA Default";
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; }
 
 
 #ifdef HAVE_DYNLOAD
@@ -132,7 +136,7 @@ constexpr char alsaDevice[] = "ALSA Default";
     MAGIC(snd_card_next);                                                     \
     MAGIC(snd_config_update_free_global)
 
-static void *alsa_handle;
+void *alsa_handle;
 #define MAKE_FUNC(f) decltype(f) * p##f
 ALSA_FUNCS(MAKE_FUNC);
 #undef MAKE_FUNC
@@ -241,61 +245,104 @@ SwParamsPtr CreateSwParams()
 struct DevMap {
     std::string name;
     std::string device_name;
+
+    template<typename T, typename U>
+    DevMap(T&& name_, U&& devname)
+        : name{std::forward<T>(name_)}, device_name{std::forward<U>(devname)}
+    { }
 };
 
-al::vector<DevMap> PlaybackDevices;
-al::vector<DevMap> CaptureDevices;
+std::vector<DevMap> PlaybackDevices;
+std::vector<DevMap> CaptureDevices;
 
 
-const char *prefix_name(snd_pcm_stream_t stream)
+std::string_view prefix_name(snd_pcm_stream_t stream) noexcept
 {
-    assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
-    return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
+    if(stream == SND_PCM_STREAM_PLAYBACK)
+        return "device-prefix"sv;
+    return "capture-prefix"sv;
 }
 
-al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
+struct SndCtlCardInfo {
+    snd_ctl_card_info_t *mInfo{};
+
+    SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); }
+    ~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); }
+    SndCtlCardInfo(const SndCtlCardInfo&) = delete;
+    SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete;
+
+    [[nodiscard]]
+    operator snd_ctl_card_info_t*() const noexcept { return mInfo; }
+};
+
+struct SndPcmInfo {
+    snd_pcm_info_t *mInfo{};
+
+    SndPcmInfo() { snd_pcm_info_malloc(&mInfo); }
+    ~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); }
+    SndPcmInfo(const SndPcmInfo&) = delete;
+    SndPcmInfo& operator=(const SndPcmInfo&) = delete;
+
+    [[nodiscard]]
+    operator snd_pcm_info_t*() const noexcept { return mInfo; }
+};
+
+struct SndCtl {
+    snd_ctl_t *mHandle{};
+
+    SndCtl() = default;
+    ~SndCtl() { if(mHandle) snd_ctl_close(mHandle); }
+    SndCtl(const SndCtl&) = delete;
+    SndCtl& operator=(const SndCtl&) = delete;
+
+    [[nodiscard]]
+    auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); }
+
+    [[nodiscard]]
+    operator snd_ctl_t*() const noexcept { return mHandle; }
+};
+
+
+std::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
 {
-    al::vector<DevMap> devlist;
+    std::vector<DevMap> devlist;
 
-    snd_ctl_card_info_t *info;
-    snd_ctl_card_info_malloc(&info);
-    snd_pcm_info_t *pcminfo;
-    snd_pcm_info_malloc(&pcminfo);
+    SndCtlCardInfo info;
+    SndPcmInfo pcminfo;
 
-    auto defname = ConfigValueStr(nullptr, "alsa",
-        (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
-    devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
+    auto defname = ConfigValueStr({}, "alsa"sv,
+        (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv);
+    devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv);
 
-    if(auto customdevs = ConfigValueStr(nullptr, "alsa",
-        (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
+    if(auto customdevs = ConfigValueStr({}, "alsa"sv,
+        (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv))
     {
-        size_t nextpos{customdevs->find_first_not_of(';')};
-        size_t curpos;
-        while((curpos=nextpos) < customdevs->length())
+        size_t curpos{customdevs->find_first_not_of(';')};
+        while(curpos < customdevs->length())
         {
-            nextpos = customdevs->find_first_of(';', curpos+1);
-
-            size_t seppos{customdevs->find_first_of('=', curpos)};
+            size_t nextpos{customdevs->find(';', curpos+1)};
+            const size_t seppos{customdevs->find('=', curpos)};
             if(seppos == curpos || seppos >= nextpos)
             {
-                std::string spec{customdevs->substr(curpos, nextpos-curpos)};
+                const std::string spec{customdevs->substr(curpos, nextpos-curpos)};
                 ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
             }
             else
             {
-                devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos),
-                    customdevs->substr(seppos+1, nextpos-seppos-1)});
-                const auto &entry = devlist.back();
+                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());
             }
 
             if(nextpos < customdevs->length())
                 nextpos = customdevs->find_first_not_of(';', nextpos+1);
+            curpos = nextpos;
         }
     }
 
-    const std::string main_prefix{
-        ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")};
+    const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream))
+        .value_or("plughw:")};
 
     int card{-1};
     int err{snd_card_next(&card)};
@@ -303,16 +350,17 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
     {
         std::string name{"hw:" + std::to_string(card)};
 
-        snd_ctl_t *handle;
-        if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0)
+        SndCtl handle;
+        err = handle.open(name.c_str(), 0);
+        if(err < 0)
         {
             ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
             continue;
         }
-        if((err=snd_ctl_card_info(handle, info)) < 0)
+        err = snd_ctl_card_info(handle, info);
+        if(err < 0)
         {
             ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
-            snd_ctl_close(handle);
             continue;
         }
 
@@ -321,11 +369,10 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
         name = prefix_name(stream);
         name += '-';
         name += cardid;
-        const std::string card_prefix{
-            ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
+        const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)};
 
         int dev{-1};
-        while(1)
+        while(true)
         {
             if(snd_ctl_pcm_next_device(handle, &dev) < 0)
                 ERR("snd_ctl_pcm_next_device failed\n");
@@ -334,7 +381,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
             snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
             snd_pcm_info_set_subdevice(pcminfo, 0);
             snd_pcm_info_set_stream(pcminfo, stream);
-            if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
+            err = snd_ctl_pcm_info(handle, pcminfo);
+            if(err < 0)
             {
                 if(err != -ENOENT)
                     ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
@@ -347,8 +395,8 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
             name += cardid;
             name += '-';
             name += std::to_string(dev);
-            const std::string device_prefix{
-                ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)};
+            const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name)
+                .value_or(card_prefix)};
 
             /* "CardName, PcmName (CARD=cardid,DEV=dev)" */
             name = cardname;
@@ -367,18 +415,13 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
             device += ",DEV=";
             device += std::to_string(dev);
             
-            devlist.emplace_back(DevMap{std::move(name), std::move(device)});
-            const auto &entry = devlist.back();
+            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());
         }
-        snd_ctl_close(handle);
     }
     if(err < 0)
         ERR("snd_card_next failed: %s\n", snd_strerror(err));
 
-    snd_pcm_info_free(pcminfo);
-    snd_ctl_card_info_free(info);
-
     return devlist;
 }
 
@@ -387,7 +430,6 @@ int verify_state(snd_pcm_t *handle)
 {
     snd_pcm_state_t state{snd_pcm_state(handle)};
 
-    int err;
     switch(state)
     {
         case SND_PCM_STATE_OPEN:
@@ -400,13 +442,14 @@ int verify_state(snd_pcm_t *handle)
             break;
 
         case SND_PCM_STATE_XRUN:
-            if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
+            if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0)
                 return err;
             break;
         case SND_PCM_STATE_SUSPENDED:
-            if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
+            if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0)
                 return err;
             break;
+
         case SND_PCM_STATE_DISCONNECTED:
             return -ENODEV;
     }
@@ -422,7 +465,7 @@ struct AlsaPlayback final : public BackendBase {
     int mixerProc();
     int mixerNoMMapProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -434,12 +477,10 @@ struct AlsaPlayback final : public BackendBase {
     std::mutex mMutex;
 
     uint mFrameStep{};
-    al::vector<al::byte> mBuffer;
+    std::vector<std::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(AlsaPlayback)
 };
 
 AlsaPlayback::~AlsaPlayback()
@@ -453,7 +494,7 @@ AlsaPlayback::~AlsaPlayback()
 int AlsaPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
     const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@@ -501,7 +542,7 @@ int AlsaPlayback::mixerProc()
         avail -= avail%update_size;
 
         // it is possible that contiguous areas are smaller, thus we use a loop
-        std::lock_guard<std::mutex> _{mMutex};
+        std::lock_guard<std::mutex> dlock{mMutex};
         while(avail > 0)
         {
             snd_pcm_uframes_t frames{avail};
@@ -515,6 +556,7 @@ int AlsaPlayback::mixerProc()
                 break;
             }
 
+            /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
             char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
             mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
 
@@ -536,7 +578,7 @@ int AlsaPlayback::mixerProc()
 int AlsaPlayback::mixerNoMMapProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
     const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
@@ -580,13 +622,13 @@ int AlsaPlayback::mixerNoMMapProc()
             continue;
         }
 
-        al::byte *WritePtr{mBuffer.data()};
+        auto WritePtr = mBuffer.begin();
         avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
-        std::lock_guard<std::mutex> _{mMutex};
-        mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
+        std::lock_guard<std::mutex> dlock{mMutex};
+        mDevice->renderSamples(al::to_address(WritePtr), static_cast<uint>(avail), mFrameStep);
         while(avail > 0)
         {
-            snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
+            snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr),
                 static_cast<snd_pcm_uframes_t>(avail))};
             switch(ret)
             {
@@ -621,11 +663,10 @@ int AlsaPlayback::mixerNoMMapProc()
 }
 
 
-void AlsaPlayback::open(const char *name)
+void AlsaPlayback::open(std::string_view name)
 {
-    al::optional<std::string> driveropt;
-    const char *driver{"default"};
-    if(name)
+    std::string driver{"default"};
+    if(!name.empty())
     {
         if(PlaybackDevices.empty())
             PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
@@ -634,22 +675,22 @@ void AlsaPlayback::open(const char *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", name};
-        driver = iter->device_name.c_str();
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
+        driver = iter->device_name;
     }
     else
     {
-        name = alsaDevice;
-        if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")})
-            driver = driveropt->c_str();
+        name = GetDefaultName();
+        if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv))
+            driver = std::move(driveropt).value();
     }
-    TRACE("Opening device \"%s\"\n", driver);
+    TRACE("Opening device \"%s\"\n", driver.c_str());
 
     snd_pcm_t *pcmHandle{};
-    int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
+    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};
+            "Could not open ALSA device \"%s\"", driver.c_str()};
     if(mPcmHandle)
         snd_pcm_close(mPcmHandle);
     mPcmHandle = pcmHandle;
@@ -688,15 +729,14 @@ bool AlsaPlayback::reset()
         break;
     }
 
-    bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)};
+    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};
 
-    int err{};
     HwParamsPtr hp{CreateHwParams()};
 #define CHECK(x) do {                                                         \
-    if((err=(x)) < 0)                                                         \
+    if(int err{x}; err < 0)                                                   \
         throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
             snd_strerror(err)};                                               \
 } while(0)
@@ -711,17 +751,18 @@ bool AlsaPlayback::reset()
     /* test and set format (implicitly sets sample bits) */
     if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0)
     {
-        static const struct {
+        struct FormatMap {
             snd_pcm_format_t format;
             DevFmtType fmttype;
-        } formatlist[] = {
-            { SND_PCM_FORMAT_FLOAT, DevFmtFloat  },
-            { SND_PCM_FORMAT_S32,   DevFmtInt    },
-            { SND_PCM_FORMAT_U32,   DevFmtUInt   },
-            { SND_PCM_FORMAT_S16,   DevFmtShort  },
-            { SND_PCM_FORMAT_U16,   DevFmtUShort },
-            { SND_PCM_FORMAT_S8,    DevFmtByte   },
-            { SND_PCM_FORMAT_U8,    DevFmtUByte  },
+        };
+        static constexpr std::array formatlist{
+            FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat },
+            FormatMap{SND_PCM_FORMAT_S32,   DevFmtInt   },
+            FormatMap{SND_PCM_FORMAT_U32,   DevFmtUInt  },
+            FormatMap{SND_PCM_FORMAT_S16,   DevFmtShort },
+            FormatMap{SND_PCM_FORMAT_U16,   DevFmtUShort},
+            FormatMap{SND_PCM_FORMAT_S8,    DevFmtByte  },
+            FormatMap{SND_PCM_FORMAT_U8,    DevFmtUByte },
         };
 
         for(const auto &fmt : formatlist)
@@ -746,7 +787,7 @@ bool AlsaPlayback::reset()
         else mDevice->FmtChans = DevFmtStereo;
     }
     /* set rate (implicitly constrains period/buffer parameters) */
-    if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
+    if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false)
         || !mDevice->Flags.test(FrequencyRequest))
     {
         if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
@@ -756,10 +797,10 @@ bool AlsaPlayback::reset()
         WARN("Failed to enable ALSA resampler\n");
     CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr));
     /* set period time (implicitly constrains period/buffer parameters) */
-    if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0)
+    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));
     /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
-    if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0)
+    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));
     /* install and prepare hardware configuration */
     CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
@@ -794,11 +835,10 @@ bool AlsaPlayback::reset()
 
 void AlsaPlayback::start()
 {
-    int err{};
     snd_pcm_access_t access{};
     HwParamsPtr hp{CreateHwParams()};
 #define CHECK(x) do {                                                         \
-    if((err=(x)) < 0)                                                         \
+    if(int err{x}; err < 0)                                                   \
         throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
             snd_strerror(err)};                                               \
 } while(0)
@@ -847,8 +887,8 @@ ClockLatency AlsaPlayback::getClockLatency()
 {
     ClockLatency ret;
 
-    std::lock_guard<std::mutex> _{mMutex};
-    ret.ClockTime = GetDeviceClockTime(mDevice);
+    std::lock_guard<std::mutex> dlock{mMutex};
+    ret.ClockTime = mDevice->getClockTime();
     snd_pcm_sframes_t delay{};
     int err{snd_pcm_delay(mPcmHandle, &delay)};
     if(err < 0)
@@ -867,23 +907,21 @@ struct AlsaCapture final : public BackendBase {
     AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~AlsaCapture() override;
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
     ClockLatency getClockLatency() override;
 
     snd_pcm_t *mPcmHandle{nullptr};
 
-    al::vector<al::byte> mBuffer;
+    std::vector<std::byte> mBuffer;
 
     bool mDoCapture{false};
     RingBufferPtr mRing{nullptr};
 
     snd_pcm_sframes_t mLastAvail{0};
-
-    DEF_NEWDEL(AlsaCapture)
 };
 
 AlsaCapture::~AlsaCapture()
@@ -894,11 +932,10 @@ AlsaCapture::~AlsaCapture()
 }
 
 
-void AlsaCapture::open(const char *name)
+void AlsaCapture::open(std::string_view name)
 {
-    al::optional<std::string> driveropt;
-    const char *driver{"default"};
-    if(name)
+    std::string driver{"default"};
+    if(!name.empty())
     {
         if(CaptureDevices.empty())
             CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
@@ -907,21 +944,20 @@ void AlsaCapture::open(const char *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", name};
-        driver = iter->device_name.c_str();
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
+        driver = iter->device_name;
     }
     else
     {
-        name = alsaDevice;
-        if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")})
-            driver = driveropt->c_str();
+        name = GetDefaultName();
+        if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv))
+            driver = std::move(driveropt).value();
     }
 
-    TRACE("Opening device \"%s\"\n", driver);
-    int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
-    if(err < 0)
+    TRACE("Opening device \"%s\"\n", driver.c_str());
+    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};
+            "Could not open ALSA device \"%s\"", driver.c_str()};
 
     /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
     snd_config_update_free_global();
@@ -952,13 +988,15 @@ void AlsaCapture::open(const char *name)
         break;
     }
 
-    snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)};
-    snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)};
+    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)};
 
     bool needring{false};
     HwParamsPtr hp{CreateHwParams()};
 #define CHECK(x) do {                                                         \
-    if((err=(x)) < 0)                                                         \
+    if(int err{x}; err < 0)                                                   \
         throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
             snd_strerror(err)};                                               \
 } while(0)
@@ -996,13 +1034,11 @@ void AlsaCapture::open(const char *name)
 
 void AlsaCapture::start()
 {
-    int err{snd_pcm_prepare(mPcmHandle)};
-    if(err < 0)
+    if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0)
         throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
             snd_strerror(err)};
 
-    err = snd_pcm_start(mPcmHandle);
-    if(err < 0)
+    if(int err{snd_pcm_start(mPcmHandle)}; err < 0)
         throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
             snd_strerror(err)};
 
@@ -1021,25 +1057,27 @@ void AlsaCapture::stop()
         /* The ring buffer implicitly captures when checking availability.
          * Direct access needs to explicitly capture it into temp storage.
          */
-        auto temp = al::vector<al::byte>(
+        auto temp = std::vector<std::byte>(
             static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
         captureSamples(temp.data(), avail);
         mBuffer = std::move(temp);
     }
-    int err{snd_pcm_drop(mPcmHandle)};
-    if(err < 0)
+    if(int err{snd_pcm_drop(mPcmHandle)}; err < 0)
         ERR("drop failed: %s\n", snd_strerror(err));
     mDoCapture = false;
 }
 
-void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
+void AlsaCapture::captureSamples(std::byte *buffer, uint samples)
 {
     if(mRing)
     {
-        mRing->read(buffer, samples);
+        std::ignore = mRing->read(buffer, samples);
         return;
     }
 
+    const auto outspan = al::span{buffer,
+        static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, samples))};
+    auto outiter = outspan.begin();
     mLastAvail -= samples;
     while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0)
     {
@@ -1052,20 +1090,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
             if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples;
 
             amt = snd_pcm_frames_to_bytes(mPcmHandle, amt);
-            std::copy_n(mBuffer.begin(), amt, buffer);
+            std::copy_n(mBuffer.begin(), amt, outiter);
 
             mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt);
             amt = snd_pcm_bytes_to_frames(mPcmHandle, amt);
         }
         else if(mDoCapture)
-            amt = snd_pcm_readi(mPcmHandle, buffer, 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)));
 
             if(amt == -EAGAIN)
                 continue;
-            if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
+            amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
+            if(amt >= 0)
             {
                 amt = snd_pcm_start(mPcmHandle);
                 if(amt >= 0)
@@ -1085,12 +1124,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
             continue;
         }
 
-        buffer = buffer + amt;
+        outiter += amt;
         samples -= static_cast<uint>(amt);
     }
     if(samples > 0)
-        std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
-            al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
+        std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples),
+            std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
 }
 
 uint AlsaCapture::availableSamples()
@@ -1102,7 +1141,8 @@ uint AlsaCapture::availableSamples()
     {
         ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
 
-        if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
+        avail = snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1);
+        if(avail >= 0)
         {
             if(mDoCapture)
                 avail = snd_pcm_start(mPcmHandle);
@@ -1138,7 +1178,8 @@ uint AlsaCapture::availableSamples()
 
             if(amt == -EAGAIN)
                 continue;
-            if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
+            amt = snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1);
+            if(amt >= 0)
             {
                 if(mDoCapture)
                     amt = snd_pcm_start(mPcmHandle);
@@ -1167,7 +1208,7 @@ ClockLatency AlsaCapture::getClockLatency()
 {
     ClockLatency ret;
 
-    ret.ClockTime = GetDeviceClockTime(mDevice);
+    ret.ClockTime = mDevice->getClockTime();
     snd_pcm_sframes_t delay{};
     int err{snd_pcm_delay(mPcmHandle, &delay)};
     if(err < 0)
@@ -1186,13 +1227,9 @@ ClockLatency AlsaCapture::getClockLatency()
 
 bool AlsaBackendFactory::init()
 {
-    bool error{false};
-
 #ifdef HAVE_DYNLOAD
     if(!alsa_handle)
     {
-        std::string missing_funcs;
-
         alsa_handle = LoadLib("libasound.so.2");
         if(!alsa_handle)
         {
@@ -1200,27 +1237,25 @@ bool AlsaBackendFactory::init()
             return false;
         }
 
-        error = false;
+        std::string missing_funcs;
 #define LOAD_FUNC(f) do {                                                     \
     p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f));      \
-    if(p##f == nullptr) {                                                     \
-        error = true;                                                         \
-        missing_funcs += "\n" #f;                                             \
-    }                                                                         \
+    if(p##f == nullptr) missing_funcs += "\n" #f;                             \
 } while(0)
         ALSA_FUNCS(LOAD_FUNC);
 #undef LOAD_FUNC
 
-        if(error)
+        if(!missing_funcs.empty())
         {
             WARN("Missing expected functions:%s\n", missing_funcs.c_str());
             CloseLib(alsa_handle);
             alsa_handle = nullptr;
+            return false;
         }
     }
 #endif
 
-    return !error;
+    return true;
 }
 
 bool AlsaBackendFactory::querySupport(BackendType type)

+ 73 - 65
libs/openal-soft/alc/backends/base.cpp

@@ -14,17 +14,32 @@
 
 #include "albit.h"
 #include "core/logging.h"
-#include "aloptional.h"
 #endif
 
 #include "atomic.h"
 #include "core/devformat.h"
 
 
+namespace al {
+
+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
+
+
 bool BackendBase::reset()
 { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
 
-void BackendBase::captureSamples(al::byte*, uint)
+void BackendBase::captureSamples(std::byte*, uint)
 { }
 
 uint BackendBase::availableSamples()
@@ -32,29 +47,28 @@ uint BackendBase::availableSamples()
 
 ClockLatency BackendBase::getClockLatency()
 {
-    ClockLatency ret;
+    ClockLatency ret{};
 
     uint refcount;
     do {
         refcount = mDevice->waitForMix();
-        ret.ClockTime = GetDeviceClockTime(mDevice);
+        ret.ClockTime = mDevice->getClockTime();
         std::atomic_thread_fence(std::memory_order_acquire);
-    } while(refcount != ReadRef(mDevice->MixCount));
+    } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed));
 
     /* NOTE: The device will generally have about all but one periods filled at
      * any given time during playback. Without a more accurate measurement from
      * the output, this is an okay approximation.
      */
-    ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
-        std::chrono::seconds::zero());
+    ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize};
     ret.Latency /= mDevice->Frequency;
 
     return ret;
 }
 
-void BackendBase::setDefaultWFXChannelOrder()
+void BackendBase::setDefaultWFXChannelOrder() const
 {
-    mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+    mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
 
     switch(mDevice->FmtChans)
     {
@@ -98,14 +112,38 @@ void BackendBase::setDefaultWFXChannelOrder()
         mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
         mDevice->RealOut.ChannelIndex[SideRight]   = 7;
         break;
+    case DevFmtX714:
+        mDevice->RealOut.ChannelIndex[FrontLeft]     = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]    = 1;
+        mDevice->RealOut.ChannelIndex[FrontCenter]   = 2;
+        mDevice->RealOut.ChannelIndex[LFE]           = 3;
+        mDevice->RealOut.ChannelIndex[BackLeft]      = 4;
+        mDevice->RealOut.ChannelIndex[BackRight]     = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]      = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]     = 7;
+        mDevice->RealOut.ChannelIndex[TopFrontLeft]  = 8;
+        mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
+        mDevice->RealOut.ChannelIndex[TopBackLeft]   = 10;
+        mDevice->RealOut.ChannelIndex[TopBackRight]  = 11;
+        break;
+    case DevFmtX3D71:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+        mDevice->RealOut.ChannelIndex[LFE]         = 3;
+        mDevice->RealOut.ChannelIndex[Aux0]        = 4;
+        mDevice->RealOut.ChannelIndex[Aux1]        = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 7;
+        break;
     case DevFmtAmbi3D:
         break;
     }
 }
 
-void BackendBase::setDefaultChannelOrder()
+void BackendBase::setDefaultChannelOrder() const
 {
-    mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+    mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
 
     switch(mDevice->FmtChans)
     {
@@ -127,6 +165,30 @@ void BackendBase::setDefaultChannelOrder()
         mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
         mDevice->RealOut.ChannelIndex[SideRight]   = 7;
         return;
+    case DevFmtX714:
+        mDevice->RealOut.ChannelIndex[FrontLeft]     = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]    = 1;
+        mDevice->RealOut.ChannelIndex[BackLeft]      = 2;
+        mDevice->RealOut.ChannelIndex[BackRight]     = 3;
+        mDevice->RealOut.ChannelIndex[FrontCenter]   = 4;
+        mDevice->RealOut.ChannelIndex[LFE]           = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]      = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]     = 7;
+        mDevice->RealOut.ChannelIndex[TopFrontLeft]  = 8;
+        mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
+        mDevice->RealOut.ChannelIndex[TopBackLeft]   = 10;
+        mDevice->RealOut.ChannelIndex[TopBackRight]  = 11;
+        break;
+    case DevFmtX3D71:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[Aux0]        = 2;
+        mDevice->RealOut.ChannelIndex[Aux1]        = 3;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
+        mDevice->RealOut.ChannelIndex[LFE]         = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 7;
+        return;
 
     /* Same as WFX order */
     case DevFmtMono:
@@ -138,57 +200,3 @@ void BackendBase::setDefaultChannelOrder()
         break;
     }
 }
-
-#ifdef _WIN32
-void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
-{
-    static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
-        | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT};
-    static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER
-        | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT};
-    /* Swap a 5.1 mask using the back channels for one with the sides. */
-    if(chanmask == x51rear) chanmask = x51;
-
-    auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
-    {
-        switch(chanbit)
-        {
-        case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
-        case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
-        case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
-        case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
-        case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
-        case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
-        case SPEAKER_FRONT_LEFT_OF_CENTER: break;
-        case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
-        case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
-        case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
-        case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
-        case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
-        case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
-        case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
-        case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
-        case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
-        case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
-        case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
-        }
-        WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
-        return al::nullopt;
-    };
-
-    const uint numchans{mDevice->channelsFromFmt()};
-    uint idx{0};
-    while(chanmask)
-    {
-        const int bit{al::countr_zero(chanmask)};
-        const uint mask{1u << bit};
-        chanmask &= ~mask;
-
-        if(auto label = get_channel(mask))
-        {
-            mDevice->RealOut.ChannelIndex[*label] = idx;
-            if(++idx == numchans) break;
-        }
-    }
-}
-#endif

+ 18 - 35
libs/openal-soft/alc/backends/base.h

@@ -3,13 +3,15 @@
 
 #include <chrono>
 #include <cstdarg>
+#include <cstddef>
 #include <memory>
 #include <ratio>
 #include <string>
+#include <string_view>
 
-#include "albyte.h"
 #include "core/device.h"
 #include "core/except.h"
+#include "alc/events.h"
 
 
 using uint = unsigned int;
@@ -20,13 +22,13 @@ struct ClockLatency {
 };
 
 struct BackendBase {
-    virtual void open(const char *name) = 0;
+    virtual void open(std::string_view name) = 0;
 
     virtual bool reset();
     virtual void start() = 0;
     virtual void stop() = 0;
 
-    virtual void captureSamples(al::byte *buffer, uint samples);
+    virtual void captureSamples(std::byte *buffer, uint samples);
     virtual uint availableSamples();
 
     virtual ClockLatency getClockLatency();
@@ -38,14 +40,9 @@ struct BackendBase {
 
 protected:
     /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
-    void setDefaultChannelOrder();
+    void setDefaultChannelOrder() const;
     /** Sets the default channel order used by WaveFormatEx. */
-    void setDefaultWFXChannelOrder();
-
-#ifdef _WIN32
-    /** Sets the channel order given the WaveFormatEx mask. */
-    void setChannelOrderFromWFXMask(uint chanmask);
-#endif
+    void setDefaultWFXChannelOrder() const;
 };
 using BackendPtr = std::unique_ptr<BackendBase>;
 
@@ -55,18 +52,6 @@ enum class BackendType {
 };
 
 
-/* Helper to get the current clock time from the device's ClockBase, and
- * SamplesDone converted from the sample rate.
- */
-inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
-{
-    using std::chrono::seconds;
-    using std::chrono::nanoseconds;
-
-    auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
-    return device->ClockBase + ns;
-}
-
 /* Helper to get the device latency from the backend, including any fixed
  * latency from post-processing.
  */
@@ -79,16 +64,18 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
 
 
 struct BackendFactory {
+    virtual ~BackendFactory() = default;
+
     virtual bool init() = 0;
 
     virtual bool querySupport(BackendType type) = 0;
 
+    virtual alc::EventSupport queryEventSupport(alc::EventType, BackendType)
+    { return alc::EventSupport::NoSupport; }
+
     virtual std::string probe(BackendType type) = 0;
 
     virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
-
-protected:
-    virtual ~BackendFactory() = default;
 };
 
 namespace al {
@@ -103,19 +90,15 @@ class backend_exception final : public base_exception {
     backend_error mErrorCode;
 
 public:
-#ifdef __USE_MINGW_ANSI_STDIO
-    [[gnu::format(gnu_printf, 3, 4)]]
+#ifdef __MINGW32__
+    [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
 #else
     [[gnu::format(printf, 3, 4)]]
 #endif
-    backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
-    {
-        std::va_list args;
-        va_start(args, msg);
-        setMessage(msg, args);
-        va_end(args);
-    }
-    backend_error errorCode() const noexcept { return mErrorCode; }
+    backend_exception(backend_error code, const char *msg, ...);
+    ~backend_exception() override;
+
+    [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; }
 };
 
 } // namespace al

+ 211 - 129
libs/openal-soft/alc/backends/coreaudio.cpp

@@ -22,18 +22,20 @@
 
 #include "coreaudio.h"
 
-#include <inttypes.h>
+#include <cinttypes>
+#include <cmath>
+#include <memory>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string>
 #include <string.h>
 #include <unistd.h>
-
-#include <cmath>
-#include <memory>
-#include <string>
+#include <vector>
+#include <optional>
 
 #include "alnumeric.h"
+#include "alstring.h"
 #include "core/converter.h"
 #include "core/device.h"
 #include "core/logging.h"
@@ -42,15 +44,39 @@
 #include <AudioUnit/AudioUnit.h>
 #include <AudioToolbox/AudioToolbox.h>
 
-
-namespace {
-
 #if TARGET_OS_IOS || TARGET_OS_TV
 #define CAN_ENUMERATE 0
 #else
+#include <IOKit/audio/IOAudioTypes.h>
 #define CAN_ENUMERATE 1
 #endif
 
+namespace {
+
+constexpr auto OutputElement = 0;
+constexpr auto InputElement = 1;
+
+struct FourCCPrinter {
+    char mString[sizeof(UInt32) + 1]{};
+
+    constexpr FourCCPrinter(UInt32 code) noexcept
+    {
+        for(size_t i{0};i < sizeof(UInt32);++i)
+        {
+            const auto ch = static_cast<char>(code & 0xff);
+            /* If this breaks early it'll leave the first byte null, to get
+             * read as a 0-length string.
+             */
+            if(ch <= 0x1f || ch >= 0x7f)
+                break;
+            mString[sizeof(UInt32)-1-i] = ch;
+            code >>= 8;
+        }
+    }
+    constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast<UInt32>(code)} { }
+
+    constexpr const char *c_str() const noexcept { return mString; }
+};
 
 #if CAN_ENUMERATE
 struct DeviceEntry {
@@ -145,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
         &propSize);
     if(err)
     {
-        ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
+        ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n",
+            FourCCPrinter{err}.c_str(), err);
         return 0;
     }
 
@@ -156,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
         buflist);
     if(err)
     {
-        ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
+        ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n",
+            FourCCPrinter{err}.c_str(), err);
         return 0;
     }
 
@@ -180,7 +208,7 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
     auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
     if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
     {
-        ERR("Failed to get device list: %u\n", err);
+        ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
         return;
     }
 
@@ -245,6 +273,48 @@ void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
     newdevs.swap(list);
 }
 
+struct DeviceHelper {
+    DeviceHelper()
+    {
+        AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
+            kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
+        OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
+        if (status != noErr)
+            ERR("AudioObjectAddPropertyListener fail: %d", status);
+    }
+    ~DeviceHelper()
+    {
+        AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice,
+            kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
+        OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil);
+        if (status != noErr)
+            ERR("AudioObjectRemovePropertyListener fail: %d", status);
+    }
+
+    static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses,
+        const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/)
+    {
+        for(UInt32 i = 0; i < inNumberAddresses; ++i)
+        {
+            switch(inAddresses[i].mSelector)
+            {
+            case kAudioHardwarePropertyDefaultOutputDevice:
+            case kAudioHardwarePropertyDefaultSystemOutputDevice:
+                alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
+                    "Default playback device changed: "+std::to_string(inAddresses[i].mSelector));
+                break;
+            case kAudioHardwarePropertyDefaultInputDevice:
+                alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
+                    "Default capture device changed: "+std::to_string(inAddresses[i].mSelector));
+                break;
+            }
+        }
+        return noErr;
+    }
+};
+
+static std::optional<DeviceHelper> sDeviceHelper;
+
 #else
 
 static constexpr char ca_device[] = "CoreAudio Default";
@@ -258,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase {
     OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
         const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
         AudioBufferList *ioData) noexcept;
-    static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
-        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
-        AudioBufferList *ioData) noexcept
-    {
-        return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
-            inBusNumber, inNumberFrames, ioData);
-    }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -275,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase {
 
     uint mFrameSize{0u};
     AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
-
-    DEF_NEWDEL(CoreAudioPlayback)
 };
 
 CoreAudioPlayback::~CoreAudioPlayback()
@@ -299,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
 }
 
 
-void CoreAudioPlayback::open(const char *name)
+void CoreAudioPlayback::open(std::string_view name)
 {
 #if CAN_ENUMERATE
     AudioDeviceID audioDevice{kAudioDeviceUnknown};
-    if(!name)
+    if(name.empty())
         GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
             &audioDevice);
     else
@@ -316,16 +377,16 @@ void CoreAudioPlayback::open(const char *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", name};
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
 
         audioDevice = devmatch->mId;
     }
 #else
-    if(!name)
+    if(name.empty())
         name = ca_device;
-    else if(strcmp(name, ca_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            name};
+    else if(name != ca_device)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
+            al::sizei(name), name.data()};
 #endif
 
     /* open the default output unit */
@@ -349,18 +410,18 @@ void CoreAudioPlayback::open(const char *name)
     OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
     if(err != noErr)
         throw al::backend_exception{al::backend_error::NoDevice,
-            "Could not create component instance: %u", err};
+            "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
 #if CAN_ENUMERATE
     if(audioDevice != kAudioDeviceUnknown)
         AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
-            kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
+            kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
 #endif
 
     err = AudioUnitInitialize(audioUnit);
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not initialize audio unit: %u", err};
+            "Could not initialize audio unit: '%s' (%u)", 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.
@@ -373,19 +434,34 @@ void CoreAudioPlayback::open(const char *name)
     mAudioUnit = audioUnit;
 
 #if CAN_ENUMERATE
-    if(name)
+    if(!name.empty())
         mDevice->DeviceName = name;
     else
     {
         UInt32 propSize{sizeof(audioDevice)};
         audioDevice = kAudioDeviceUnknown;
         AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
-            kAudioUnitScope_Global, 0, &audioDevice, &propSize);
+            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(audioDevice != kAudioDeviceUnknown)
+    {
+        UInt32 type{};
+        err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false,
+            kAudioObjectPropertyElementMaster, sizeof(type), &type);
+        if(err != noErr)
+            ERR("Failed to get audio device type: %u\n", err);
+        else
+        {
+            TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str());
+            mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones));
+        }
+    }
+
 #else
     mDevice->DeviceName = name;
 #endif
@@ -395,16 +471,17 @@ bool CoreAudioPlayback::reset()
 {
     OSStatus err{AudioUnitUninitialize(mAudioUnit)};
     if(err != noErr)
-        ERR("-- AudioUnitUninitialize failed.\n");
+        ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
 
     /* retrieve default output unit's properties (output side) */
     AudioStreamBasicDescription streamFormat{};
     UInt32 size{sizeof(streamFormat)};
     err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
-        0, &streamFormat, &size);
+        OutputElement, &streamFormat, &size);
     if(err != noErr || size != sizeof(streamFormat))
     {
-        ERR("AudioUnitGetProperty failed\n");
+        ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
+            err);
         return false;
     }
 
@@ -423,8 +500,8 @@ bool CoreAudioPlayback::reset()
      */
     if(mDevice->Frequency != streamFormat.mSampleRate)
     {
-        mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
-            streamFormat.mSampleRate / mDevice->Frequency);
+        mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
+            mDevice->Frequency + 0.5);
         mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
     }
 
@@ -468,10 +545,11 @@ bool CoreAudioPlayback::reset()
     streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
 
     err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
-        0, &streamFormat, sizeof(streamFormat));
+        OutputElement, &streamFormat, sizeof(streamFormat));
     if(err != noErr)
     {
-        ERR("AudioUnitSetProperty failed\n");
+        ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(),
+            err);
         return false;
     }
 
@@ -480,14 +558,16 @@ bool CoreAudioPlayback::reset()
     /* setup callback */
     mFrameSize = mDevice->frameSizeFromFmt();
     AURenderCallbackStruct input{};
-    input.inputProc = CoreAudioPlayback::MixerProcC;
+    input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
+    { return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
     input.inputProcRefCon = this;
 
     err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
-        kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
+        kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct));
     if(err != noErr)
     {
-        ERR("AudioUnitSetProperty failed\n");
+        ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n",
+            FourCCPrinter{err}.c_str(), err);
         return false;
     }
 
@@ -495,7 +575,7 @@ bool CoreAudioPlayback::reset()
     err = AudioUnitInitialize(mAudioUnit);
     if(err != noErr)
     {
-        ERR("AudioUnitInitialize failed\n");
+        ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
         return false;
     }
 
@@ -507,14 +587,14 @@ void CoreAudioPlayback::start()
     const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "AudioOutputUnitStart failed: %d", err};
+            "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 }
 
 void CoreAudioPlayback::stop()
 {
     OSStatus err{AudioOutputUnitStop(mAudioUnit)};
     if(err != noErr)
-        ERR("AudioOutputUnitStop failed\n");
+        ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
 }
 
 
@@ -525,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase {
     OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
         const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
         UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
-    static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
-        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
-        AudioBufferList *ioData) noexcept
-    {
-        return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
-            inBusNumber, inNumberFrames, ioData);
-    }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     AudioUnit mAudioUnit{0};
@@ -546,9 +619,9 @@ struct CoreAudioCapture final : public BackendBase {
 
     SampleConverterPtr mConverter;
 
-    RingBufferPtr mRing{nullptr};
+    std::vector<char> mCaptureData;
 
-    DEF_NEWDEL(CoreAudioCapture)
+    RingBufferPtr mRing{nullptr};
 };
 
 CoreAudioCapture::~CoreAudioCapture()
@@ -559,58 +632,38 @@ CoreAudioCapture::~CoreAudioCapture()
 }
 
 
-OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
-    const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
+OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
+    const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
     AudioBufferList*) noexcept
 {
-    AudioUnitRenderActionFlags flags = 0;
     union {
-        al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
+        std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
         AudioBufferList list;
     } audiobuf{};
 
-    auto rec_vec = mRing->getWriteVector();
-    inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
-        rec_vec.first.len+rec_vec.second.len));
+    audiobuf.list.mNumberBuffers = 1;
+    audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
+    audiobuf.list.mBuffers[0].mData = mCaptureData.data();
+    audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
 
-    // Fill the ringbuffer's two segments with data from the input device
-    if(rec_vec.first.len >= inNumberFrames)
-    {
-        audiobuf.list.mNumberBuffers = 1;
-        audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
-        audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
-        audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
-    }
-    else
-    {
-        const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
-        audiobuf.list.mNumberBuffers = 2;
-        audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
-        audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
-        audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
-            mFormat.mBytesPerFrame;
-        audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
-        audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
-        audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
-    }
-    OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
+    OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
         inNumberFrames, &audiobuf.list)};
     if(err != noErr)
     {
-        ERR("AudioUnitRender error: %d\n", err);
+        ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
         return err;
     }
 
-    mRing->writeAdvance(inNumberFrames);
+    std::ignore = mRing->write(mCaptureData.data(), inNumberFrames);
     return noErr;
 }
 
 
-void CoreAudioCapture::open(const char *name)
+void CoreAudioCapture::open(std::string_view name)
 {
 #if CAN_ENUMERATE
     AudioDeviceID audioDevice{kAudioDeviceUnknown};
-    if(!name)
+    if(name.empty())
         GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
             &audioDevice);
     else
@@ -623,16 +676,16 @@ void CoreAudioCapture::open(const char *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", name};
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
 
         audioDevice = devmatch->mId;
     }
 #else
-    if(!name)
+    if(name.empty())
         name = ca_device;
-    else if(strcmp(name, ca_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            name};
+    else if(name != ca_device)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
+            al::sizei(name), name.data()};
 #endif
 
     AudioComponentDescription desc{};
@@ -656,63 +709,67 @@ void CoreAudioCapture::open(const char *name)
     OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
     if(err != noErr)
         throw al::backend_exception{al::backend_error::NoDevice,
-            "Could not create component instance: %u", err};
-
-#if CAN_ENUMERATE
-    if(audioDevice != kAudioDeviceUnknown)
-        AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
-            kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID));
-#endif
+            "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
     // Turn off AudioUnit output
     UInt32 enableIO{0};
     err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
-        kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
+        kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not disable audio unit output property: %u", err};
+            "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(),
+            err};
 
     // Turn on AudioUnit input
     enableIO = 1;
     err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
-        kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO));
+        kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not enable audio unit input property: %u", err};
+            "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(),
+            err};
+
+#if CAN_ENUMERATE
+    if(audioDevice != kAudioDeviceUnknown)
+        AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+            kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
+#endif
 
     // set capture callback
     AURenderCallbackStruct input{};
-    input.inputProc = CoreAudioCapture::RecordProcC;
+    input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept
+    { return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); };
     input.inputProcRefCon = this;
 
     err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
-        kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
+        kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not set capture callback: %u", err};
+            "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
     // Disable buffer allocation for capture
     UInt32 flag{0};
     err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
-        kAudioUnitScope_Output, 1, &flag, sizeof(flag));
+        kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not disable buffer allocation property: %u", err};
+            "Could not disable buffer allocation property: '%s' (%u)", 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: %u", err};
+            "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
     // Get the hardware format
     AudioStreamBasicDescription hardwareFormat{};
     UInt32 propertySize{sizeof(hardwareFormat)};
     err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
-        1, &hardwareFormat, &propertySize);
+        InputElement, &hardwareFormat, &propertySize);
     if(err != noErr || propertySize != sizeof(hardwareFormat))
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not get input format: %u", err};
+            "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
     // Set up the requested format description
     AudioStreamBasicDescription requestedFormat{};
@@ -764,6 +821,8 @@ void CoreAudioCapture::open(const char *name)
     case DevFmtX51:
     case DevFmtX61:
     case DevFmtX71:
+    case DevFmtX714:
+    case DevFmtX3D71:
     case DevFmtAmbi3D:
         throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
             DevFmtChannelsString(mDevice->FmtChans)};
@@ -788,17 +847,17 @@ void CoreAudioCapture::open(const char *name)
     // The output format should be the requested format, but using the hardware sample rate
     // This is because the AudioUnit will automatically scale other properties, except for sample rate
     err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
-        1, &outputFormat, sizeof(outputFormat));
+        InputElement, &outputFormat, sizeof(outputFormat));
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Could not set input format: %u", err};
+            "Could not set input format: '%s' (%u)", 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{double{outputFormat.mSampleRate} / mDevice->Frequency};
-    auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
-        static_cast<UInt32>(outputFormat.mSampleRate)/10);
+    double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
+    auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*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,
@@ -807,29 +866,31 @@ void CoreAudioCapture::open(const char *name)
     UInt32 outputFrameCount{};
     propertySize = sizeof(outputFrameCount);
     err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
-        kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize);
+        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: %u", err};
+            "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 
-    outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
+    mCaptureData.resize(outputFrameCount * mFrameSize);
+
+    outputFrameCount = static_cast<UInt32>(std::max(uint64_t{outputFrameCount}, FrameCount64));
     mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
 
     /* Set up sample converter if needed */
     if(outputFormat.mSampleRate != mDevice->Frequency)
-        mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
+        mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
             mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
             mDevice->Frequency, Resampler::FastBSinc24);
 
 #if CAN_ENUMERATE
-    if(name)
+    if(!name.empty())
         mDevice->DeviceName = name;
     else
     {
         UInt32 propSize{sizeof(audioDevice)};
         audioDevice = kAudioDeviceUnknown;
         AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
-            kAudioUnitScope_Global, 0, &audioDevice, &propSize);
+            kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
 
         std::string devname{GetDeviceName(audioDevice)};
         if(!devname.empty()) mDevice->DeviceName = std::move(devname);
@@ -846,21 +907,21 @@ void CoreAudioCapture::start()
     OSStatus err{AudioOutputUnitStart(mAudioUnit)};
     if(err != noErr)
         throw al::backend_exception{al::backend_error::DeviceError,
-            "AudioOutputUnitStart failed: %d", err};
+            "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err};
 }
 
 void CoreAudioCapture::stop()
 {
     OSStatus err{AudioOutputUnitStop(mAudioUnit)};
     if(err != noErr)
-        ERR("AudioOutputUnitStop failed\n");
+        ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err);
 }
 
-void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
+void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples)
 {
     if(!mConverter)
     {
-        mRing->read(buffer, samples);
+        std::ignore = mRing->read(buffer, samples);
         return;
     }
 
@@ -894,7 +955,13 @@ BackendFactory &CoreAudioBackendFactory::getFactory()
     return factory;
 }
 
-bool CoreAudioBackendFactory::init() { return true; }
+bool CoreAudioBackendFactory::init() 
+{ 
+#if CAN_ENUMERATE
+    sDeviceHelper.emplace();
+#endif
+    return true; 
+}
 
 bool CoreAudioBackendFactory::querySupport(BackendType type)
 { return type == BackendType::Playback || type == BackendType::Capture; }
@@ -942,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp
         return BackendPtr{new CoreAudioCapture{device}};
     return nullptr;
 }
+
+alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
+{
+    switch(eventType)
+    {
+    case alc::EventType::DefaultDeviceChanged:
+        return alc::EventSupport::FullSupport;
+
+    case alc::EventType::DeviceAdded:
+    case alc::EventType::DeviceRemoved:
+    case alc::EventType::Count:
+        break;
+    }
+    return alc::EventSupport::NoSupport;
+}

+ 2 - 0
libs/openal-soft/alc/backends/coreaudio.h

@@ -9,6 +9,8 @@ public:
 
     bool querySupport(BackendType type) override;
 
+    alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
+
     std::string probe(BackendType type) override;
 
     BackendPtr createBackend(DeviceBase *device, BackendType type) override;

+ 91 - 95
libs/openal-soft/alc/backends/dsound.cpp

@@ -25,10 +25,6 @@
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
 #include <cguid.h>
 #include <mmreg.h>
 #ifndef _WAVEFORMATEXTENSIBLE_
@@ -36,15 +32,23 @@
 #include <ksmedia.h>
 #endif
 
+#include <algorithm>
 #include <atomic>
 #include <cassert>
-#include <thread>
+#include <cstdio>
+#include <cstdlib>
+#include <functional>
+#include <memory.h>
+#include <mutex>
 #include <string>
+#include <thread>
 #include <vector>
-#include <algorithm>
-#include <functional>
 
+#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"
@@ -52,7 +56,6 @@
 #include "dynload.h"
 #include "ringbuffer.h"
 #include "strutils.h"
-#include "threads.h"
 
 /* MinGW-w64 needs this for some unknown reason now. */
 using LPCWAVEFORMATEX = const WAVEFORMATEX*;
@@ -115,6 +118,7 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac
 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
 #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
 
 #define MAX_UPDATES 128
 
@@ -128,10 +132,10 @@ struct DevMap {
     { }
 };
 
-al::vector<DevMap> PlaybackDevices;
-al::vector<DevMap> CaptureDevices;
+std::vector<DevMap> PlaybackDevices;
+std::vector<DevMap> CaptureDevices;
 
-bool checkName(const al::vector<DevMap> &list, const std::string &name)
+bool checkName(const al::span<DevMap> list, const std::string &name)
 {
     auto match_name = [&name](const DevMap &entry) -> bool
     { return entry.name == name; };
@@ -143,7 +147,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
     if(!guid)
         return TRUE;
 
-    auto& devices = *static_cast<al::vector<DevMap>*>(data);
+    auto& devices = *static_cast<std::vector<DevMap>*>(data);
     const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
 
     int count{1};
@@ -175,7 +179,7 @@ struct DSoundPlayback final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -188,8 +192,6 @@ struct DSoundPlayback final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(DSoundPlayback)
 };
 
 DSoundPlayback::~DSoundPlayback()
@@ -208,7 +210,7 @@ DSoundPlayback::~DSoundPlayback()
 FORCE_ALIGN int DSoundPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     DSBCAPS DSBCaps{};
     DSBCaps.dwSize = sizeof(DSBCaps);
@@ -298,24 +300,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
     return 0;
 }
 
-void DSoundPlayback::open(const char *name)
+void DSoundPlayback::open(std::string_view name)
 {
     HRESULT hr;
     if(PlaybackDevices.empty())
     {
         /* Initialize COM to prevent name truncation */
-        HRESULT hrcom{CoInitialize(nullptr)};
+        ComWrapper com{};
         hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
         if(FAILED(hr))
             ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
-        if(SUCCEEDED(hrcom))
-            CoUninitialize();
     }
 
     const GUID *guid{nullptr};
-    if(!name && !PlaybackDevices.empty())
+    if(name.empty() && !PlaybackDevices.empty())
     {
-        name = PlaybackDevices[0].name.c_str();
+        name = PlaybackDevices[0].name;
         guid = &PlaybackDevices[0].guid;
     }
     else
@@ -331,7 +331,7 @@ void DSoundPlayback::open(const char *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", name};
+                    "Device name \"%.*s\" not found", al::sizei(name), name.data()};
         }
         guid = &iter->guid;
     }
@@ -346,7 +346,7 @@ void DSoundPlayback::open(const char *name)
     //DirectSound Init code
     ComPtr<IDirectSound> ds;
     if(SUCCEEDED(hr))
-        hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
+        hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr);
     if(SUCCEEDED(hr))
         hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
     if(FAILED(hr))
@@ -389,52 +389,56 @@ bool DSoundPlayback::reset()
     }
 
     WAVEFORMATEXTENSIBLE OutputType{};
-    DWORD speakers;
+    DWORD speakers{};
     HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
-    if(SUCCEEDED(hr))
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to get speaker config: 0x%08lx", hr};
+
+    speakers = DSSPEAKER_CONFIG(speakers);
+    if(!mDevice->Flags.test(ChannelsRequest))
     {
-        speakers = DSSPEAKER_CONFIG(speakers);
-        if(!mDevice->Flags.test(ChannelsRequest))
-        {
-            if(speakers == DSSPEAKER_MONO)
-                mDevice->FmtChans = DevFmtMono;
-            else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
-                mDevice->FmtChans = DevFmtStereo;
-            else if(speakers == DSSPEAKER_QUAD)
-                mDevice->FmtChans = DevFmtQuad;
-            else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
-                mDevice->FmtChans = DevFmtX51;
-            else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
-                mDevice->FmtChans = DevFmtX71;
-            else
-                ERR("Unknown system speaker config: 0x%lx\n", speakers);
-        }
-        mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
+        if(speakers == DSSPEAKER_MONO)
+            mDevice->FmtChans = DevFmtMono;
+        else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
+            mDevice->FmtChans = DevFmtStereo;
+        else if(speakers == DSSPEAKER_QUAD)
+            mDevice->FmtChans = DevFmtQuad;
+        else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
+            mDevice->FmtChans = DevFmtX51;
+        else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
+            mDevice->FmtChans = DevFmtX71;
+        else
+            ERR("Unknown system speaker config: 0x%lx\n", speakers);
+    }
+    mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
+    const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
 
-        switch(mDevice->FmtChans)
-        {
-        case DevFmtMono: OutputType.dwChannelMask = MONO; break;
-        case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
-            /*fall-through*/
-        case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
-        case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
-        case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
-        case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
-        case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
-        }
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono: OutputType.dwChannelMask = MONO; break;
+    case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
+        /* fall-through */
+    case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
+    case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
+    case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
+    case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
+    case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
+    case 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;
-    }
+    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)
     {
@@ -455,7 +459,7 @@ retry_open:
             DSBUFFERDESC DSBDescription{};
             DSBDescription.dwSize = sizeof(DSBDescription);
             DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
-            hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
+            hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr);
         }
         if(SUCCEEDED(hr))
             hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
@@ -475,7 +479,7 @@ retry_open:
         DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
         DSBDescription.lpwfxFormat = &OutputType.Format;
 
-        hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
+        hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr);
         if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
         {
             mDevice->FmtType = DevFmtShort;
@@ -485,12 +489,9 @@ retry_open:
 
     if(SUCCEEDED(hr))
     {
-        void *ptr;
-        hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
+        hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies));
         if(SUCCEEDED(hr))
         {
-            mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
-
             uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
             assert(num_updates <= MAX_UPDATES);
 
@@ -514,7 +515,7 @@ retry_open:
     }
 
     ResetEvent(mNotifyEvent);
-    setChannelOrderFromWFXMask(OutputType.dwChannelMask);
+    setDefaultWFXChannelOrder();
 
     return true;
 }
@@ -545,10 +546,10 @@ struct DSoundCapture final : public BackendBase {
     DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~DSoundCapture() override;
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     ComPtr<IDirectSoundCapture> mDSC;
@@ -557,8 +558,6 @@ struct DSoundCapture final : public BackendBase {
     DWORD mCursor{0u};
 
     RingBufferPtr mRing;
-
-    DEF_NEWDEL(DSoundCapture)
 };
 
 DSoundCapture::~DSoundCapture()
@@ -572,24 +571,22 @@ DSoundCapture::~DSoundCapture()
 }
 
 
-void DSoundCapture::open(const char *name)
+void DSoundCapture::open(std::string_view name)
 {
     HRESULT hr;
     if(CaptureDevices.empty())
     {
         /* Initialize COM to prevent name truncation */
-        HRESULT hrcom{CoInitialize(nullptr)};
+        ComWrapper com{};
         hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
         if(FAILED(hr))
             ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
-        if(SUCCEEDED(hrcom))
-            CoUninitialize();
     }
 
     const GUID *guid{nullptr};
-    if(!name && !CaptureDevices.empty())
+    if(name.empty() && !CaptureDevices.empty())
     {
-        name = CaptureDevices[0].name.c_str();
+        name = CaptureDevices[0].name;
         guid = &CaptureDevices[0].guid;
     }
     else
@@ -605,7 +602,7 @@ void DSoundCapture::open(const char *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", name};
+                    "Device name \"%.*s\" not found", al::sizei(name), name.data()};
         }
         guid = &iter->guid;
     }
@@ -635,6 +632,8 @@ void DSoundCapture::open(const char *name)
     case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
     case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
     case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
+    case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
+    case DevFmtX3D71:
     case DevFmtAmbi3D:
         WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
@@ -662,8 +661,7 @@ void DSoundCapture::open(const char *name)
         InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
     }
 
-    uint samples{mDevice->BufferSize};
-    samples = maxu(samples, 100 * mDevice->Frequency / 1000);
+    const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
 
     DSCBUFFERDESC DSCBDescription{};
     DSCBDescription.dwSize = sizeof(DSCBDescription);
@@ -672,9 +670,9 @@ void DSoundCapture::open(const char *name)
     DSCBDescription.lpwfxFormat = &InputType.Format;
 
     //DirectSoundCapture Init code
-    hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
+    hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr);
     if(SUCCEEDED(hr))
-        mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
+        mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr);
     if(SUCCEEDED(hr))
          mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
 
@@ -689,7 +687,7 @@ void DSoundCapture::open(const char *name)
     }
 
     mBufferBytes = DSCBDescription.dwBufferBytes;
-    setChannelOrderFromWFXMask(InputType.dwChannelMask);
+    setDefaultWFXChannelOrder();
 
     mDevice->DeviceName = name;
 }
@@ -712,8 +710,8 @@ void DSoundCapture::stop()
     }
 }
 
-void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void DSoundCapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 uint DSoundCapture::availableSamples()
 {
@@ -736,9 +734,9 @@ uint DSoundCapture::availableSamples()
     }
     if(SUCCEEDED(hr))
     {
-        mRing->write(ReadPtr1, ReadCnt1/FrameSize);
+        std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize);
         if(ReadPtr2 != nullptr && ReadCnt2 > 0)
-            mRing->write(ReadPtr2, ReadCnt2/FrameSize);
+            std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize);
         hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
         mCursor = ReadCursor;
     }
@@ -807,8 +805,8 @@ std::string DSoundBackendFactory::probe(BackendType type)
     };
 
     /* Initialize COM to prevent name truncation */
+    ComWrapper com{};
     HRESULT hr;
-    HRESULT hrcom{CoInitialize(nullptr)};
     switch(type)
     {
     case BackendType::Playback:
@@ -827,8 +825,6 @@ std::string DSoundBackendFactory::probe(BackendType type)
         std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
         break;
     }
-    if(SUCCEEDED(hrcom))
-        CoUninitialize();
 
     return outnames;
 }

+ 92 - 103
libs/openal-soft/alc/backends/jack.cpp

@@ -22,23 +22,27 @@
 
 #include "jack.h"
 
+#include <array>
 #include <cstdlib>
 #include <cstdio>
 #include <cstring>
 #include <memory.h>
-
-#include <array>
+#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 "ringbuffer.h"
-#include "threads.h"
 
 #include <jack/jack.h>
 #include <jack/ringbuffer.h>
@@ -46,6 +50,8 @@
 
 namespace {
 
+using namespace std::string_view_literals;
+
 #ifdef HAVE_DYNLOAD
 #define JACK_FUNCS(MAGIC)          \
     MAGIC(jack_client_open);       \
@@ -99,19 +105,13 @@ decltype(jack_error_callback) * pjack_error_callback;
 #endif
 
 
-constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
-
 jack_options_t ClientOptions = JackNullOption;
 
 bool jack_load()
 {
-    bool error{false};
-
 #ifdef HAVE_DYNLOAD
     if(!jack_handle)
     {
-        std::string missing_funcs;
-
 #ifdef _WIN32
 #define JACKLIB "libjack.dll"
 #else
@@ -124,13 +124,10 @@ bool jack_load()
             return false;
         }
 
-        error = false;
+        std::string missing_funcs;
 #define LOAD_FUNC(f) do {                                                     \
     p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f));      \
-    if(p##f == nullptr) {                                                     \
-        error = true;                                                         \
-        missing_funcs += "\n" #f;                                             \
-    }                                                                         \
+    if(p##f == nullptr) missing_funcs += "\n" #f;                             \
 } while(0)
         JACK_FUNCS(LOAD_FUNC);
 #undef LOAD_FUNC
@@ -139,56 +136,58 @@ bool jack_load()
         LOAD_SYM(jack_error_callback);
 #undef LOAD_SYM
 
-        if(error)
+        if(!missing_funcs.empty())
         {
             WARN("Missing expected functions:%s\n", missing_funcs.c_str());
             CloseLib(jack_handle);
             jack_handle = nullptr;
+            return false;
         }
     }
 #endif
 
-    return !error;
+    return true;
 }
 
 
 struct JackDeleter {
     void operator()(void *ptr) { jack_free(ptr); }
 };
-using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
+using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>; /* NOLINT(*-avoid-c-arrays) */
 
 struct DeviceEntry {
     std::string mName;
     std::string mPattern;
+
+    template<typename T, typename U>
+    DeviceEntry(T&& name, U&& pattern)
+        : mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
+    { }
 };
 
-al::vector<DeviceEntry> PlaybackList;
+std::vector<DeviceEntry> PlaybackList;
 
 
-void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
+void EnumerateDevices(jack_client_t *client, std::vector<DeviceEntry> &list)
 {
     std::remove_reference_t<decltype(list)>{}.swap(list);
 
-    if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
+    if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)})
     {
         for(size_t i{0};ports[i];++i)
         {
-            const char *sep{std::strchr(ports[i], ':')};
-            if(!sep || ports[i] == sep) continue;
+            const std::string_view portname{ports[i]};
+            const size_t seppos{portname.find(':')};
+            if(seppos == 0 || seppos >= portname.size())
+                continue;
 
-            const al::span<const char> portdev{ports[i], sep};
+            const std::string_view portdev{ports[i], seppos};
             auto check_name = [portdev](const DeviceEntry &entry) -> bool
-            {
-                const size_t len{portdev.size()};
-                return entry.mName.length() == len
-                    && entry.mName.compare(0, len, portdev.data(), len) == 0;
-            };
+            { return entry.mName == portdev; };
             if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
                 continue;
 
-            std::string name{portdev.data(), portdev.size()};
-            list.emplace_back(DeviceEntry{name, name+":"});
-            const auto &entry = list.back();
+            const auto &entry = list.emplace_back(portdev, std::string{portdev}+":");
             TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
         }
         /* There are ports but couldn't get device names from them. Add a
@@ -197,11 +196,11 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
         if(ports[0] && list.empty())
         {
             WARN("No device names found in available ports, adding a generic name.\n");
-            list.emplace_back(DeviceEntry{"JACK", ""});
+            list.emplace_back("JACK"sv, ""sv);
         }
     }
 
-    if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
+    if(auto listopt = ConfigValueStr({}, "jack", "custom-devices"))
     {
         for(size_t strpos{0};strpos < listopt->size();)
         {
@@ -216,31 +215,25 @@ void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
                 continue;
             }
 
-            const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
-            const al::span<const char> pattern{listopt->data()+(seppos+1),
-                std::min(nextpos, listopt->size())-(seppos+1)};
+            const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos);
+            const auto pattern = std::string_view{*listopt}.substr(seppos+1,
+                std::min(nextpos, listopt->size())-(seppos+1));
 
             /* Check if this custom pattern already exists in the list. */
             auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
-            {
-                const size_t len{pattern.size()};
-                return entry.mPattern.length() == len
-                    && entry.mPattern.compare(0, len, pattern.data(), len) == 0;
-            };
+            { return entry.mPattern == pattern; };
             auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
             if(itemmatch != list.end())
             {
                 /* If so, replace the name with this custom one. */
-                itemmatch->mName.assign(name.data(), name.size());
+                itemmatch->mName = name;
                 TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
                     itemmatch->mPattern.c_str());
             }
             else
             {
                 /* Otherwise, add a new device entry. */
-                list.emplace_back(DeviceEntry{std::string{name.data(), name.size()},
-                    std::string{pattern.data(), pattern.size()}});
-                const auto &entry = list.back();
+                const auto &entry = list.emplace_back(name, pattern);
                 TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
             }
 
@@ -290,7 +283,7 @@ struct JackPlayback final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -299,7 +292,7 @@ struct JackPlayback final : public BackendBase {
     std::string mPortPattern;
 
     jack_client_t *mClient{nullptr};
-    std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
+    std::array<jack_port_t*,MaxOutputChannels> mPort{};
 
     std::mutex mMutex;
 
@@ -310,8 +303,6 @@ struct JackPlayback final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(JackPlayback)
 };
 
 JackPlayback::~JackPlayback()
@@ -331,7 +322,7 @@ JackPlayback::~JackPlayback()
 
 int JackPlayback::processRt(jack_nframes_t numframes) noexcept
 {
-    std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+    std::array<jack_default_audio_sample_t*,MaxOutputChannels> out;
     size_t numchans{0};
     for(auto port : mPort)
     {
@@ -340,7 +331,7 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
         out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
     }
 
-    if LIKELY(mPlaying.load(std::memory_order_acquire))
+    if(mPlaying.load(std::memory_order_acquire)) LIKELY
         mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
     else
     {
@@ -355,52 +346,50 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept
 
 int JackPlayback::process(jack_nframes_t numframes) noexcept
 {
-    std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+    std::array<al::span<float>,MaxOutputChannels> out;
     size_t numchans{0};
     for(auto port : mPort)
     {
         if(!port) break;
-        out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+        out[numchans++] = {static_cast<float*>(jack_port_get_buffer(port, numframes)), numframes};
     }
 
     jack_nframes_t total{0};
-    if LIKELY(mPlaying.load(std::memory_order_acquire))
+    if(mPlaying.load(std::memory_order_acquire)) LIKELY
     {
         auto data = mRing->getReadVector();
-        jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
-        auto write_first = [&data,numchans,todo](float *outbuf) -> float*
+        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 float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
-            auto deinterlace_input = [&in,numchans]() noexcept -> float
+            auto in = firstin.cbegin();
+            auto deinterlace_input = [&in,c,numchans]() noexcept -> float
             {
-                float ret{*in};
-                in += numchans;
+                const float ret{in[c]};
+                in += ptrdiff_t(numchans);
                 return ret;
             };
-            std::generate_n(outbuf, todo, deinterlace_input);
-            data.first.buf += sizeof(float);
-            return outbuf + todo;
-        };
-        std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
+            std::generate_n(out[c].begin(), todo, deinterlace_input);
+            out[c] = out[c].subspan(todo);
+        }
         total += todo;
 
-        todo = minu(numframes-total, static_cast<uint>(data.second.len));
+        todo = std::min(numframes-total, static_cast<jack_nframes_t>(data.second.len));
         if(todo > 0)
         {
-            auto write_second = [&data,numchans,todo](float *outbuf) -> float*
+            auto secondin = al::span{reinterpret_cast<const float*>(data.second.buf), todo};
+            for(size_t c{0};c < numchans;++c)
             {
-                const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
-                auto deinterlace_input = [&in,numchans]() noexcept -> float
+                auto in = secondin.cbegin();
+                auto deinterlace_input = [&in,c,numchans]() noexcept -> float
                 {
-                    float ret{*in};
-                    in += numchans;
+                    float ret{in[c]};
+                    in += ptrdiff_t(numchans);
                     return ret;
                 };
-                std::generate_n(outbuf, todo, deinterlace_input);
-                data.second.buf += sizeof(float);
-                return outbuf + todo;
-            };
-            std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
+                std::generate_n(out[c].begin(), todo, deinterlace_input);
+                out[c] = out[c].subspan(todo);
+            }
             total += todo;
         }
 
@@ -410,8 +399,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
 
     if(numframes > total)
     {
-        const jack_nframes_t todo{numframes - total};
-        auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
+        auto clear_buf = [](const al::span<float> outbuf) -> void
+        { std::fill(outbuf.begin(), outbuf.end(), 0.0f); };
         std::for_each(out.begin(), out.begin()+numchans, clear_buf);
     }
 
@@ -421,7 +410,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept
 int JackPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const size_t frame_step{mDevice->channelsFromFmt()};
 
@@ -438,10 +427,10 @@ int JackPlayback::mixerProc()
         size_t todo{data.first.len + data.second.len};
         todo -= todo%mDevice->UpdateSize;
 
-        const auto len1 = static_cast<uint>(minz(data.first.len, todo));
-        const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
+        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));
 
-        std::lock_guard<std::mutex> _{mMutex};
+        std::lock_guard<std::mutex> dlock{mMutex};
         mDevice->renderSamples(data.first.buf, len1, frame_step);
         if(len2 > 0)
             mDevice->renderSamples(data.second.buf, len2, frame_step);
@@ -452,14 +441,14 @@ int JackPlayback::mixerProc()
 }
 
 
-void JackPlayback::open(const char *name)
+void JackPlayback::open(std::string_view name)
 {
     if(!mClient)
     {
         const PathNamePair &binname = GetProcBinary();
         const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
 
-        jack_status_t status;
+        jack_status_t status{};
         mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
         if(mClient == nullptr)
             throw al::backend_exception{al::backend_error::DeviceError,
@@ -476,9 +465,9 @@ void JackPlayback::open(const char *name)
     if(PlaybackList.empty())
         EnumerateDevices(mClient, PlaybackList);
 
-    if(!name && !PlaybackList.empty())
+    if(name.empty() && !PlaybackList.empty())
     {
-        name = PlaybackList[0].mName.c_str();
+        name = PlaybackList[0].mName;
         mPortPattern = PlaybackList[0].mPattern;
     }
     else
@@ -488,14 +477,10 @@ void JackPlayback::open(const char *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", name?name:""};
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
         mPortPattern = iter->mPattern;
     }
 
-    mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
-    jack_set_process_callback(mClient,
-        mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
-
     mDevice->DeviceName = name;
 }
 
@@ -506,6 +491,10 @@ bool JackPlayback::reset()
     std::for_each(mPort.begin(), mPort.end(), unregister_port);
     mPort.fill(nullptr);
 
+    mRTMixing = GetConfigValueBool(mDevice->DeviceName, "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.
      */
@@ -520,9 +509,9 @@ bool JackPlayback::reset()
     }
     else
     {
-        const char *devname{mDevice->DeviceName.c_str()};
+        const std::string_view devname{mDevice->DeviceName};
         uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
-        bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+        bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
         mDevice->BufferSize = bufsize + mDevice->UpdateSize;
     }
 
@@ -535,7 +524,7 @@ bool JackPlayback::reset()
     while(bad_port != ports_end)
     {
         std::string name{"channel_" + std::to_string(++port_num)};
-        *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
+        *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
             JackPortIsOutput | JackPortIsTerminal, 0);
         if(!*bad_port) break;
         ++bad_port;
@@ -570,10 +559,10 @@ void JackPlayback::start()
     if(jack_activate(mClient))
         throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
 
-    const char *devname{mDevice->DeviceName.c_str()};
+    const std::string_view devname{mDevice->DeviceName};
     if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
     {
-        JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
+        JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE,
             JackPortIsInput)};
         if(!pnames)
         {
@@ -581,7 +570,7 @@ void JackPlayback::start()
             throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
         }
 
-        for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
+        for(size_t i{0};i < std::size(mPort) && mPort[i];++i)
         {
             if(!pnames[i])
             {
@@ -608,7 +597,7 @@ void JackPlayback::start()
     else
     {
         uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
-        bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+        bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize);
         mDevice->BufferSize = bufsize + mDevice->UpdateSize;
 
         mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
@@ -648,8 +637,8 @@ ClockLatency JackPlayback::getClockLatency()
 {
     ClockLatency ret;
 
-    std::lock_guard<std::mutex> _{mMutex};
-    ret.ClockTime = GetDeviceClockTime(mDevice);
+    std::lock_guard<std::mutex> dlock{mMutex};
+    ret.ClockTime = mDevice->getClockTime();
     ret.Latency  = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
     ret.Latency /= mDevice->Frequency;
 
@@ -669,7 +658,7 @@ bool JackBackendFactory::init()
     if(!jack_load())
         return false;
 
-    if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
+    if(!GetConfigValueBool({}, "jack", "spawn-server", false))
         ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
 
     const PathNamePair &binname = GetProcBinary();
@@ -677,7 +666,7 @@ bool JackBackendFactory::init()
 
     void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
     jack_set_error_function(jack_msg_handler);
-    jack_status_t status;
+    jack_status_t status{};
     jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
     jack_set_error_function(old_error_cb);
     if(!client)
@@ -706,7 +695,7 @@ std::string JackBackendFactory::probe(BackendType type)
 
     const PathNamePair &binname = GetProcBinary();
     const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
-    jack_status_t status;
+    jack_status_t status{};
     switch(type)
     {
     case BackendType::Playback:

+ 2 - 4
libs/openal-soft/alc/backends/loopback.cpp

@@ -30,16 +30,14 @@ namespace {
 struct LoopbackBackend final : public BackendBase {
     LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
-
-    DEF_NEWDEL(LoopbackBackend)
 };
 
 
-void LoopbackBackend::open(const char *name)
+void LoopbackBackend::open(std::string_view name)
 {
     mDevice->DeviceName = name;
 }

+ 16 - 18
libs/openal-soft/alc/backends/null.cpp

@@ -30,10 +30,11 @@
 #include <functional>
 #include <thread>
 
-#include "core/device.h"
 #include "almalloc.h"
+#include "alstring.h"
+#include "althrd_setname.h"
+#include "core/device.h"
 #include "core/helpers.h"
-#include "threads.h"
 
 
 namespace {
@@ -41,8 +42,9 @@ namespace {
 using std::chrono::seconds;
 using std::chrono::milliseconds;
 using std::chrono::nanoseconds;
+using namespace std::string_view_literals;
 
-constexpr char nullDevice[] = "No Output";
+[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; }
 
 
 struct NullBackend final : public BackendBase {
@@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(NullBackend)
 };
 
 int NullBackend::mixerProc()
@@ -66,7 +66,7 @@ int NullBackend::mixerProc()
     const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
 
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     int64_t done{0};
     auto start = std::chrono::steady_clock::now();
@@ -105,13 +105,13 @@ int NullBackend::mixerProc()
 }
 
 
-void NullBackend::open(const char *name)
+void NullBackend::open(std::string_view name)
 {
-    if(!name)
-        name = nullDevice;
-    else if(strcmp(name, nullDevice) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     mDevice->DeviceName = name;
 }
@@ -152,17 +152,15 @@ bool NullBackendFactory::querySupport(BackendType type)
 
 std::string NullBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
-        /* Includes null char. */
-        outnames.append(nullDevice, sizeof(nullDevice));
-        break;
+        /* Include null char. */
+        return std::string{GetDeviceName()} + '\0';
     case BackendType::Capture:
         break;
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)

+ 63 - 31
libs/openal-soft/alc/backends/oboe.cpp

@@ -4,10 +4,11 @@
 #include "oboe.h"
 
 #include <cassert>
+#include <cstdint>
 #include <cstring>
-#include <stdint.h>
 
 #include "alnumeric.h"
+#include "alstring.h"
 #include "core/device.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
@@ -17,7 +18,9 @@
 
 namespace {
 
-constexpr char device_name[] = "Oboe Default";
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; }
 
 
 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
@@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback
     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
         int32_t numFrames) override;
 
-    void open(const char *name) override;
+    void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override;
+
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
     return oboe::DataCallbackResult::Continue;
 }
 
+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));
+}
 
-void OboePlayback::open(const char *name)
+void OboePlayback::open(std::string_view name)
 {
-    if(!name)
-        name = device_name;
-    else if(std::strcmp(name, device_name) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     /* Open a basic output stream, just to ensure it can work. */
     oboe::ManagedStream stream;
@@ -72,6 +83,7 @@ bool OboePlayback::reset()
     oboe::AudioStreamBuilder builder;
     builder.setDirection(oboe::Direction::Output);
     builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
+    builder.setUsage(oboe::Usage::Game);
     /* Don't let Oboe convert. We should be able to handle anything it gives
      * back.
      */
@@ -81,7 +93,10 @@ bool OboePlayback::reset()
     builder.setCallback(this);
 
     if(mDevice->Flags.test(FrequencyRequest))
+    {
+        builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
         builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
+    }
     if(mDevice->Flags.test(ChannelsRequest))
     {
         /* Only use mono or stereo at user request. There's no telling what
@@ -104,6 +119,10 @@ bool OboePlayback::reset()
             break;
         case DevFmtInt:
         case DevFmtUInt:
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+            format = oboe::AudioFormat::I32;
+            break;
+#endif
         case DevFmtFloat:
             format = oboe::AudioFormat::Float;
             break;
@@ -128,7 +147,7 @@ bool OboePlayback::reset()
     if(result != oboe::Result::OK)
         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
             oboe::convertToText(result)};
-    mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
+    mStream->setBufferSizeInFrames(std::min(static_cast<int32_t>(mDevice->BufferSize),
         mStream->getBufferCapacityInFrames()));
     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
 
@@ -152,6 +171,15 @@ bool OboePlayback::reset()
     case oboe::AudioFormat::Float:
         mDevice->FmtType = DevFmtFloat;
         break;
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+    case oboe::AudioFormat::I32:
+        mDevice->FmtType = DevFmtInt;
+        break;
+    case oboe::AudioFormat::I24:
+#endif
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8)
+    case oboe::AudioFormat::IEC61937:
+#endif
     case oboe::AudioFormat::Unspecified:
     case oboe::AudioFormat::Invalid:
         throw al::backend_exception{al::backend_error::DeviceError,
@@ -164,9 +192,9 @@ bool OboePlayback::reset()
      * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
      * update size.
      */
-    mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
+    mDevice->UpdateSize = std::max(mDevice->Frequency/100u,
         static_cast<uint32_t>(mStream->getFramesPerBurst()));
-    mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
+    mDevice->BufferSize = std::max(mDevice->UpdateSize*2u,
         static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
 
     return true;
@@ -184,8 +212,7 @@ void OboePlayback::stop()
 {
     oboe::Result result{mStream->stop()};
     if(result != oboe::Result::OK)
-        throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
-            oboe::convertToText(result)};
+        ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
 }
 
 
@@ -199,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback
     oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
         int32_t numFrames) override;
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 };
 
 oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
     int32_t numFrames)
 {
-    mRing->write(audioData, static_cast<uint32_t>(numFrames));
+    std::ignore = mRing->write(audioData, static_cast<uint32_t>(numFrames));
     return oboe::DataCallbackResult::Continue;
 }
 
 
-void OboeCapture::open(const char *name)
+void OboeCapture::open(std::string_view name)
 {
-    if(!name)
-        name = device_name;
-    else if(std::strcmp(name, device_name) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     oboe::AudioStreamBuilder builder;
     builder.setDirection(oboe::Direction::Input)
@@ -245,6 +272,8 @@ void OboeCapture::open(const char *name)
     case DevFmtX51:
     case DevFmtX61:
     case DevFmtX71:
+    case DevFmtX714:
+    case DevFmtX3D71:
     case DevFmtAmbi3D:
         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
             DevFmtChannelsString(mDevice->FmtChans)};
@@ -261,10 +290,14 @@ void OboeCapture::open(const char *name)
     case DevFmtFloat:
         builder.setFormat(oboe::AudioFormat::Float);
         break;
+    case DevFmtInt:
+#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
+        builder.setFormat(oboe::AudioFormat::I32);
+        break;
+#endif
     case DevFmtByte:
     case DevFmtUByte:
     case DevFmtUShort:
-    case DevFmtInt:
     case DevFmtUInt:
         throw al::backend_exception{al::backend_error::DeviceError,
             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
@@ -278,7 +311,7 @@ void OboeCapture::open(const char *name)
     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
 
     /* Ensure a minimum ringbuffer size of 100ms. */
-    mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
+    mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u),
         static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
 
     mDevice->DeviceName = name;
@@ -296,15 +329,14 @@ void OboeCapture::stop()
 {
     const oboe::Result result{mStream->stop()};
     if(result != oboe::Result::OK)
-        throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
-            oboe::convertToText(result)};
+        ERR("Failed to stop stream: %s\n", oboe::convertToText(result));
 }
 
 uint OboeCapture::availableSamples()
 { return static_cast<uint>(mRing->readSpace()); }
 
-void OboeCapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void OboeCapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 } // namespace
 
@@ -319,8 +351,8 @@ std::string OboeBackendFactory::probe(BackendType type)
     {
     case BackendType::Playback:
     case BackendType::Capture:
-        /* Includes null char. */
-        return std::string{device_name, sizeof(device_name)};
+        /* Include null char. */
+        return std::string{GetDeviceName()} + '\0';
     }
     return std::string{};
 }

+ 103 - 163
libs/openal-soft/alc/backends/opensl.cpp

@@ -23,23 +23,26 @@
 
 #include "opensl.h"
 
-#include <stdlib.h>
 #include <jni.h>
 
-#include <new>
 #include <array>
+#include <cstdlib>
 #include <cstring>
+#include <mutex>
+#include <new>
 #include <thread>
 #include <functional>
 
 #include "albit.h"
 #include "alnumeric.h"
+#include "alsem.h"
+#include "alstring.h"
+#include "althrd_setname.h"
 #include "core/device.h"
 #include "core/helpers.h"
 #include "core/logging.h"
 #include "opthelpers.h"
 #include "ringbuffer.h"
-#include "threads.h"
 
 #include <SLES/OpenSLES.h>
 #include <SLES/OpenSLES_Android.h>
@@ -48,15 +51,17 @@
 
 namespace {
 
+using namespace std::string_view_literals;
+
 /* Helper macros */
 #define EXTRACT_VCALL_ARGS(...)  __VA_ARGS__))
 #define VCALL(obj, func)  ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
 #define VCALL0(obj, func)  ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
 
 
-constexpr char opensl_device[] = "OpenSL";
-
+[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; }
 
+[[nodiscard]]
 constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
 {
     switch(chans)
@@ -71,9 +76,15 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
     case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
         SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
         SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
-    case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+    case DevFmtX71:
+    case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
         SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
         SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
+    case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+        SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
+        SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT |
+        SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT |
+        SL_SPEAKER_TOP_BACK_RIGHT;
     case DevFmtAmbi3D:
         break;
     }
@@ -107,7 +118,7 @@ constexpr SLuint32 GetByteOrderEndianness() noexcept
     return SL_BYTEORDER_BIGENDIAN;
 }
 
-const char *res_str(SLresult result) noexcept
+constexpr const char *res_str(SLresult result) noexcept
 {
     switch(result)
     {
@@ -141,10 +152,11 @@ const char *res_str(SLresult result) noexcept
     return "Unknown error code";
 }
 
-#define PRINTERR(x, s) do {                                                      \
-    if UNLIKELY((x) != SL_RESULT_SUCCESS)                                        \
-        ERR("%s: %s\n", (s), res_str((x)));                                      \
-} while(0)
+inline void PrintErr(SLresult res, const char *str)
+{
+    if(res != SL_RESULT_SUCCESS) UNLIKELY
+        ERR("%s: %s\n", str, res_str(res));
+}
 
 
 struct OpenSLPlayback final : public BackendBase {
@@ -152,12 +164,10 @@ struct OpenSLPlayback final : public BackendBase {
     ~OpenSLPlayback() override;
 
     void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
-    static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
-    { static_cast<OpenSLPlayback*>(context)->process(bq); }
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -182,8 +192,6 @@ struct OpenSLPlayback final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(OpenSLPlayback)
 };
 
 OpenSLPlayback::~OpenSLPlayback()
@@ -222,17 +230,17 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
 int OpenSLPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     SLPlayItf player;
     SLAndroidSimpleBufferQueueItf bufferQueue;
     SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
         &bufferQueue)};
-    PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+    PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
-        PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
+        PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY");
     }
 
     const size_t frame_step{mDevice->channelsFromFmt()};
@@ -248,11 +256,11 @@ int OpenSLPlayback::mixerProc()
             SLuint32 state{0};
 
             result = VCALL(player,GetPlayState)(&state);
-            PRINTERR(result, "player->GetPlayState");
+            PrintErr(result, "player->GetPlayState");
             if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
             {
                 result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
-                PRINTERR(result, "player->SetPlayState");
+                PrintErr(result, "player->SetPlayState");
             }
             if(SL_RESULT_SUCCESS != result)
             {
@@ -289,7 +297,7 @@ int OpenSLPlayback::mixerProc()
             }
 
             result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
-            PRINTERR(result, "bufferQueue->Enqueue");
+            PrintErr(result, "bufferQueue->Enqueue");
             if(SL_RESULT_SUCCESS != result)
             {
                 mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
@@ -305,39 +313,39 @@ int OpenSLPlayback::mixerProc()
 }
 
 
-void OpenSLPlayback::open(const char *name)
+void OpenSLPlayback::open(std::string_view name)
 {
-    if(!name)
-        name = opensl_device;
-    else if(strcmp(name, opensl_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     /* There's only one device, so if it's already open, there's nothing to do. */
     if(mEngineObj) return;
 
     // create engine
     SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
-    PRINTERR(result, "slCreateEngine");
+    PrintErr(result, "slCreateEngine");
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "engine->Realize");
+        PrintErr(result, "engine->Realize");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
-        PRINTERR(result, "engine->GetInterface");
+        PrintErr(result, "engine->GetInterface");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
-        PRINTERR(result, "engine->CreateOutputMix");
+        PrintErr(result, "engine->CreateOutputMix");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "outputMix->Realize");
+        PrintErr(result, "outputMix->Realize");
     }
 
     if(SL_RESULT_SUCCESS != result)
@@ -368,74 +376,6 @@ bool OpenSLPlayback::reset()
 
     mRing = nullptr;
 
-#if 0
-    if(!mDevice->Flags.get<FrequencyRequest>())
-    {
-        /* FIXME: Disabled until I figure out how to get the Context needed for
-         * the getSystemService call.
-         */
-        JNIEnv *env = Android_GetJNIEnv();
-        jobject jctx = Android_GetContext();
-
-        /* Get necessary stuff for using java.lang.Integer,
-         * android.content.Context, and android.media.AudioManager.
-         */
-        jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
-        jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
-            "parseInt", "(Ljava/lang/String;)I"
-        );
-        TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
-
-        jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
-        jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
-            "AUDIO_SERVICE", "Ljava/lang/String;"
-        );
-        jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
-            "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
-        );
-        TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
-              ctx_cls, ctx_audsvc, ctx_getSysSvc);
-
-        jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
-        jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
-            "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
-        );
-        jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
-            "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
-        );
-        TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
-              audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
-
-        const char *strchars;
-        jstring strobj;
-
-        /* Now make the calls. */
-        //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
-        strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
-        jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
-        strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
-        TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
-        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
-
-        //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
-        strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
-        jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
-        strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
-        TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
-        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
-
-        //int sampleRate = Integer.parseInt(srateStr);
-        sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
-
-        strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
-        TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
-        JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
-
-        if(!sampleRate) sampleRate = device->Frequency;
-        else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
-    }
-#endif
-
     mDevice->FmtChans = DevFmtStereo;
     mDevice->FmtType = DevFmtShort;
 
@@ -505,20 +445,20 @@ bool OpenSLPlayback::reset()
 
         result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
             ids.data(), reqs.data());
-        PRINTERR(result, "engine->CreateAudioPlayer");
+        PrintErr(result, "engine->CreateAudioPlayer");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         /* Set the stream type to "media" (games, music, etc), if possible. */
         SLAndroidConfigurationItf config;
         result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
-        PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
+        PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
         if(SL_RESULT_SUCCESS == result)
         {
             SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
             result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
                 sizeof(streamType));
-            PRINTERR(result, "config->SetConfiguration");
+            PrintErr(result, "config->SetConfiguration");
         }
 
         /* Clear any error since this was optional. */
@@ -527,7 +467,7 @@ bool OpenSLPlayback::reset()
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "bufferQueue->Realize");
+        PrintErr(result, "bufferQueue->Realize");
     }
     if(SL_RESULT_SUCCESS == result)
     {
@@ -554,11 +494,13 @@ void OpenSLPlayback::start()
     SLAndroidSimpleBufferQueueItf bufferQueue;
     SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
         &bufferQueue)};
-    PRINTERR(result, "bufferQueue->GetInterface");
+    PrintErr(result, "bufferQueue->GetInterface");
     if(SL_RESULT_SUCCESS == result)
     {
-        result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
-        PRINTERR(result, "bufferQueue->RegisterCallback");
+        result = VCALL(bufferQueue,RegisterCallback)(
+            [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
+            { static_cast<OpenSLPlayback*>(context)->process(bq); }, this);
+        PrintErr(result, "bufferQueue->RegisterCallback");
     }
     if(SL_RESULT_SUCCESS != result)
         throw al::backend_exception{al::backend_error::DeviceError,
@@ -584,25 +526,25 @@ void OpenSLPlayback::stop()
 
     SLPlayItf player;
     SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
-    PRINTERR(result, "bufferQueue->GetInterface");
+    PrintErr(result, "bufferQueue->GetInterface");
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
-        PRINTERR(result, "player->SetPlayState");
+        PrintErr(result, "player->SetPlayState");
     }
 
     SLAndroidSimpleBufferQueueItf bufferQueue;
     result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
-    PRINTERR(result, "bufferQueue->GetInterface");
+    PrintErr(result, "bufferQueue->GetInterface");
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL0(bufferQueue,Clear)();
-        PRINTERR(result, "bufferQueue->Clear");
+        PrintErr(result, "bufferQueue->Clear");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
-        PRINTERR(result, "bufferQueue->RegisterCallback");
+        PrintErr(result, "bufferQueue->RegisterCallback");
     }
     if(SL_RESULT_SUCCESS == result)
     {
@@ -611,7 +553,9 @@ void OpenSLPlayback::stop()
             std::this_thread::yield();
             result = VCALL(bufferQueue,GetState)(&state);
         } while(SL_RESULT_SUCCESS == result && state.count > 0);
-        PRINTERR(result, "bufferQueue->GetState");
+        PrintErr(result, "bufferQueue->GetState");
+
+        mRing->reset();
     }
 }
 
@@ -619,8 +563,8 @@ ClockLatency OpenSLPlayback::getClockLatency()
 {
     ClockLatency ret;
 
-    std::lock_guard<std::mutex> _{mMutex};
-    ret.ClockTime = GetDeviceClockTime(mDevice);
+    std::lock_guard<std::mutex> dlock{mMutex};
+    ret.ClockTime = mDevice->getClockTime();
     ret.Latency  = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
     ret.Latency /= mDevice->Frequency;
 
@@ -633,13 +577,11 @@ struct OpenSLCapture final : public BackendBase {
     ~OpenSLCapture() override;
 
     void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
-    static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
-    { static_cast<OpenSLCapture*>(context)->process(bq); }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     /* engine interfaces */
@@ -653,8 +595,6 @@ struct OpenSLCapture final : public BackendBase {
     uint mSplOffset{0u};
 
     uint mFrameSize{0};
-
-    DEF_NEWDEL(OpenSLCapture)
 };
 
 OpenSLCapture::~OpenSLCapture()
@@ -677,34 +617,34 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
 }
 
 
-void OpenSLCapture::open(const char* name)
+void OpenSLCapture::open(std::string_view name)
 {
-    if(!name)
-        name = opensl_device;
-    else if(strcmp(name, opensl_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
-    PRINTERR(result, "slCreateEngine");
+    PrintErr(result, "slCreateEngine");
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "engine->Realize");
+        PrintErr(result, "engine->Realize");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
-        PRINTERR(result, "engine->GetInterface");
+        PrintErr(result, "engine->GetInterface");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         mFrameSize = mDevice->frameSizeFromFmt();
         /* Ensure the total length is at least 100ms */
-        uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
+        uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
         /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
-        uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
-            mDevice->Frequency/100*5)};
+        uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u,
+            mDevice->Frequency/100u*5u)};
         uint num_updates{(length+update_len-1) / update_len};
 
         mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
@@ -770,7 +710,7 @@ void OpenSLCapture::open(const char* name)
                 result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
                     ids.size(), ids.data(), reqs.data());
             }
-            PRINTERR(result, "engine->CreateAudioRecorder");
+            PrintErr(result, "engine->CreateAudioRecorder");
         }
     }
     if(SL_RESULT_SUCCESS == result)
@@ -778,13 +718,13 @@ void OpenSLCapture::open(const char* name)
         /* Set the record preset to "generic", if possible. */
         SLAndroidConfigurationItf config;
         result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
-        PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
+        PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
         if(SL_RESULT_SUCCESS == result)
         {
             SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
             result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
                 sizeof(preset));
-            PRINTERR(result, "config->SetConfiguration");
+            PrintErr(result, "config->SetConfiguration");
         }
 
         /* Clear any error since this was optional. */
@@ -793,24 +733,26 @@ void OpenSLCapture::open(const char* name)
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "recordObj->Realize");
+        PrintErr(result, "recordObj->Realize");
     }
 
     SLAndroidSimpleBufferQueueItf bufferQueue;
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
-        PRINTERR(result, "recordObj->GetInterface");
+        PrintErr(result, "recordObj->GetInterface");
     }
     if(SL_RESULT_SUCCESS == result)
     {
-        result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
-        PRINTERR(result, "bufferQueue->RegisterCallback");
+        result = VCALL(bufferQueue,RegisterCallback)(
+            [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
+            { static_cast<OpenSLCapture*>(context)->process(bq); }, this);
+        PrintErr(result, "bufferQueue->RegisterCallback");
     }
     if(SL_RESULT_SUCCESS == result)
     {
         const uint chunk_size{mDevice->UpdateSize * mFrameSize};
-        const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
+        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);
@@ -818,12 +760,12 @@ void OpenSLCapture::open(const char* name)
         for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
         {
             result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
+            PrintErr(result, "bufferQueue->Enqueue");
         }
         for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
         {
             result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
+            PrintErr(result, "bufferQueue->Enqueue");
         }
     }
 
@@ -849,12 +791,12 @@ void OpenSLCapture::start()
 {
     SLRecordItf record;
     SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
-    PRINTERR(result, "recordObj->GetInterface");
+    PrintErr(result, "recordObj->GetInterface");
 
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
-        PRINTERR(result, "record->SetRecordState");
+        PrintErr(result, "record->SetRecordState");
     }
     if(SL_RESULT_SUCCESS != result)
         throw al::backend_exception{al::backend_error::DeviceError,
@@ -865,16 +807,16 @@ void OpenSLCapture::stop()
 {
     SLRecordItf record;
     SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
-    PRINTERR(result, "recordObj->GetInterface");
+    PrintErr(result, "recordObj->GetInterface");
 
     if(SL_RESULT_SUCCESS == result)
     {
         result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
-        PRINTERR(result, "record->SetRecordState");
+        PrintErr(result, "record->SetRecordState");
     }
 }
 
-void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
+void OpenSLCapture::captureSamples(std::byte *buffer, uint samples)
 {
     const uint update_size{mDevice->UpdateSize};
     const uint chunk_size{update_size * mFrameSize};
@@ -886,7 +828,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
     auto rdata = mRing->getReadVector();
     for(uint i{0};i < samples;)
     {
-        const uint rem{minu(samples - i, update_size - mSplOffset)};
+        const uint rem{std::min(samples - i, update_size - mSplOffset)};
         std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
             buffer + i*size_t{mFrameSize});
 
@@ -908,22 +850,22 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
     }
 
     SLAndroidSimpleBufferQueueItf bufferQueue{};
-    if(likely(mDevice->Connected.load(std::memory_order_acquire)))
+    if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY
     {
         const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
             &bufferQueue)};
-        PRINTERR(result, "recordObj->GetInterface");
-        if(unlikely(SL_RESULT_SUCCESS != result))
+        PrintErr(result, "recordObj->GetInterface");
+        if(SL_RESULT_SUCCESS != result) UNLIKELY
         {
             mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
             bufferQueue = nullptr;
         }
     }
-    if(unlikely(!bufferQueue) || adv_count == 0)
+    if(!bufferQueue || adv_count == 0)
         return;
 
     /* For each buffer chunk that was fully read, queue another writable buffer
-     * chunk to keep the OpenSL queue full. This is rather convulated, as a
+     * chunk to keep the OpenSL queue full. This is rather convoluted, as a
      * result of the ring buffer holding more elements than are writable at a
      * given time. The end of the write vector increments when the read pointer
      * advances, which will "expose" a previously unwritable element. So for
@@ -934,14 +876,14 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
 
     SLresult result{SL_RESULT_SUCCESS};
     auto wdata = mRing->getWriteVector();
-    if(likely(adv_count > wdata.second.len))
+    if(adv_count > wdata.second.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);
         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");
+            PrintErr(result, "bufferQueue->Enqueue");
         }
     }
     if(wdata.second.len > 0)
@@ -951,7 +893,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
         for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++)
         {
             result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
+            PrintErr(result, "bufferQueue->Enqueue");
         }
     }
 }
@@ -968,16 +910,14 @@ bool OSLBackendFactory::querySupport(BackendType type)
 
 std::string OSLBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
     case BackendType::Capture:
-        /* Includes null char. */
-        outnames.append(opensl_device, sizeof(opensl_device));
-        break;
+        /* Include null char. */
+        return std::string{GetDeviceName()} + '\0';
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)

+ 126 - 106
libs/openal-soft/alc/backends/oss.cpp

@@ -31,27 +31,26 @@
 #include <algorithm>
 #include <atomic>
 #include <cerrno>
-#include <cstdio>
 #include <cstring>
 #include <exception>
 #include <functional>
 #include <memory>
-#include <new>
 #include <string>
+#include <string_view>
+#include <system_error>
 #include <thread>
 #include <utility>
+#include <vector>
 
-#include "albyte.h"
 #include "alc/alconfig.h"
 #include "almalloc.h"
 #include "alnumeric.h"
-#include "aloptional.h"
+#include "alstring.h"
+#include "althrd_setname.h"
 #include "core/device.h"
 #include "core/helpers.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
-#include "threads.h"
-#include "vector.h"
 
 #include <sys/soundcard.h>
 
@@ -83,117 +82,148 @@
 
 namespace {
 
-constexpr char DefaultName[] = "OSS Default";
-std::string DefaultPlayback{"/dev/dsp"};
-std::string DefaultCapture{"/dev/dsp"};
+using namespace std::string_literals;
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; }
+
+std::string DefaultPlayback{"/dev/dsp"s};
+std::string DefaultCapture{"/dev/dsp"s};
 
 struct DevMap {
     std::string name;
     std::string device_name;
+
+    template<typename T, typename U>
+    DevMap(T&& name_, U&& devname_)
+        : name{std::forward<T>(name_)}, device_name{std::forward<U>(devname_)}
+    { }
 };
 
-al::vector<DevMap> PlaybackDevices;
-al::vector<DevMap> CaptureDevices;
+std::vector<DevMap> PlaybackDevices;
+std::vector<DevMap> CaptureDevices;
 
 
 #ifdef ALC_OSS_COMPAT
 
 #define DSP_CAP_OUTPUT 0x00020000
 #define DSP_CAP_INPUT 0x00010000
-void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
+void ALCossListPopulate(std::vector<DevMap> &devlist, int type)
 {
-    devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
+    devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback);
 }
 
 #else
 
-void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
+class FileHandle {
+    int mFd{-1};
+
+public:
+    FileHandle() = default;
+    FileHandle(const FileHandle&) = delete;
+    FileHandle& operator=(const FileHandle&) = delete;
+    ~FileHandle() { if(mFd != -1) ::close(mFd); }
+
+    template<typename ...Args>
+    [[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool
+    {
+        close();
+        mFd = ::open(fname, std::forward<Args>(args)...);
+        return mFd != -1;
+    }
+    void close()
+    {
+        if(mFd != -1)
+            ::close(mFd);
+        mFd = -1;
+    }
+
+    [[nodiscard]]
+    auto get() const noexcept -> int { return mFd; }
+};
+
+void ALCossListAppend(std::vector<DevMap> &list, std::string_view handle, std::string_view path)
 {
 #ifdef ALC_OSS_DEVNODE_TRUC
     for(size_t i{0};i < path.size();++i)
     {
-        if(path[i] == '.' && handle.size() + i >= path.size())
+        if(path[i] == '.' && handle.size() >= path.size() - i)
         {
             const size_t hoffset{handle.size() + i - path.size()};
             if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
-                handle = handle.first(hoffset);
-            path = path.first(i);
+                handle = handle.substr(0, hoffset);
+            path = path.substr(0, i);
         }
     }
 #endif
     if(handle.empty())
         handle = path;
 
-    std::string basename{handle.data(), handle.size()};
-    std::string devname{path.data(), path.size()};
-
-    auto match_devname = [&devname](const DevMap &entry) -> bool
-    { return entry.device_name == devname; };
+    auto match_devname = [path](const DevMap &entry) -> bool
+    { return entry.device_name == path; };
     if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
         return;
 
-    auto checkName = [&list](const std::string &name) -> bool
+    auto checkName = [&list](const std::string_view name) -> bool
     {
-        auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
+        auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
         return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
     };
     int count{1};
-    std::string newname{basename};
+    std::string newname{handle};
     while(checkName(newname))
     {
-        newname = basename;
+        newname = handle;
         newname += " #";
         newname += std::to_string(++count);
     }
 
-    list.emplace_back(DevMap{std::move(newname), std::move(devname)});
-    const DevMap &entry = list.back();
+    const DevMap &entry = list.emplace_back(std::move(newname), path);
 
     TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
 }
 
-void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
+void ALCossListPopulate(std::vector<DevMap> &devlist, int type_flag)
 {
-    int fd{open("/dev/mixer", O_RDONLY)};
-    if(fd < 0)
+    oss_sysinfo si{};
+    FileHandle file;
+    if(!file.open("/dev/mixer", O_RDONLY))
     {
-        TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
+        TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str());
         goto done;
     }
 
-    oss_sysinfo si;
-    if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
+    if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1)
     {
-        TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
+        TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str());
         goto done;
     }
 
     for(int i{0};i < si.numaudios;i++)
     {
-        oss_audioinfo ai;
+        oss_audioinfo ai{};
         ai.dev = i;
-        if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
+        if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1)
         {
-            ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
+            ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i,
+                std::generic_category().message(errno).c_str());
             continue;
         }
         if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
             continue;
 
-        al::span<const char> handle;
+        std::string_view handle;
         if(ai.handle[0] != '\0')
             handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
         else
             handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
-        al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
+        const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
 
         ALCossListAppend(devlist, handle, devnode);
     }
 
 done:
-    if(fd >= 0)
-        close(fd);
-    fd = -1;
+    file.close();
 
     const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
     auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
@@ -201,7 +231,7 @@ done:
         { return entry.device_name == defdev; }
     );
     if(iter == devlist.cend())
-        devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
+        devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev});
     else
     {
         DevMap entry{std::move(*iter)};
@@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
 
     int mFd{-1};
 
-    al::vector<al::byte> mMixData;
+    std::vector<std::byte> mMixData;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(OSSPlayback)
 };
 
 OSSPlayback::~OSSPlayback()
@@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback()
 int OSSPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const size_t frame_step{mDevice->channelsFromFmt()};
     const size_t frame_size{mDevice->frameSizeFromFmt()};
@@ -269,38 +297,38 @@ int OSSPlayback::mixerProc()
         pollitem.fd = mFd;
         pollitem.events = POLLOUT;
 
-        int pret{poll(&pollitem, 1, 1000)};
-        if(pret < 0)
+        if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
         {
             if(errno == EINTR || errno == EAGAIN)
                 continue;
-            ERR("poll failed: %s\n", strerror(errno));
-            mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
+            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());
             break;
         }
-        else if(pret == 0)
+        else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
         {
             WARN("poll timeout\n");
             continue;
         }
 
-        al::byte *write_ptr{mMixData.data()};
-        size_t to_write{mMixData.size()};
-        mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
-        while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
+        al::span write_buf{mMixData};
+        mDevice->renderSamples(write_buf.data(), static_cast<uint>(write_buf.size()/frame_size),
+            frame_step);
+        while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire))
         {
-            ssize_t wrote{write(mFd, write_ptr, to_write)};
+            ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())};
             if(wrote < 0)
             {
                 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
                     continue;
-                ERR("write failed: %s\n", strerror(errno));
-                mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
+                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());
                 break;
             }
 
-            to_write -= static_cast<size_t>(wrote);
-            write_ptr += wrote;
+            write_buf = write_buf.subspan(static_cast<size_t>(wrote));
         }
     }
 
@@ -308,11 +336,11 @@ int OSSPlayback::mixerProc()
 }
 
 
-void OSSPlayback::open(const char *name)
+void OSSPlayback::open(std::string_view name)
 {
     const char *devname{DefaultPlayback.c_str()};
-    if(!name)
-        name = DefaultName;
+    if(name.empty())
+        name = GetDefaultName();
     else
     {
         if(PlaybackDevices.empty())
@@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name)
         );
         if(iter == PlaybackDevices.cend())
             throw al::backend_exception{al::backend_error::NoDevice,
-                "Device name \"%s\" not found", name};
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
         devname = iter->device_name.c_str();
     }
 
     int fd{::open(devname, O_WRONLY)};
     if(fd == -1)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
-            strerror(errno)};
+            std::generic_category().message(errno).c_str()};
 
     if(mFd != -1)
         ::close(mFd);
@@ -367,15 +395,14 @@ bool OSSPlayback::reset()
     uint ossSpeed{mDevice->Frequency};
     uint frameSize{numChannels * mDevice->bytesFromFmt()};
     /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
-    uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
+    uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)};
     uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
 
     audio_buf_info info{};
-    const char *err;
-#define CHECKERR(func) if((func) < 0) {                                       \
-    err = #func;                                                              \
-    goto err;                                                                 \
-}
+#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()};
+
     /* Don't fail if SETFRAGMENT fails. We can handle just about anything
      * that's reported back via GETOSPACE */
     ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
@@ -383,12 +410,6 @@ bool OSSPlayback::reset()
     CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
     CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
     CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
-    if(0)
-    {
-    err:
-        ERR("%s failed: %s\n", err, strerror(errno));
-        return false;
-    }
 #undef CHECKERR
 
     if(mDevice->channelsFromFmt() != numChannels)
@@ -413,7 +434,7 @@ bool OSSPlayback::reset()
 
     setDefaultChannelOrder();
 
-    mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+    mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt());
 
     return true;
 }
@@ -437,7 +458,7 @@ void OSSPlayback::stop()
     mThread.join();
 
     if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
-        ERR("Error resetting device: %s\n", strerror(errno));
+        ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
 }
 
 
@@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase {
 
     int recordProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     int mFd{-1};
@@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(OSScapture)
 };
 
 OSScapture::~OSScapture()
@@ -474,7 +493,7 @@ OSScapture::~OSScapture()
 int OSScapture::recordProc()
 {
     SetRTPriority();
-    althrd_setname(RECORD_THREAD_NAME);
+    althrd_setname(GetRecordThreadName());
 
     const size_t frame_size{mDevice->frameSizeFromFmt()};
     while(!mKillNow.load(std::memory_order_acquire))
@@ -483,16 +502,16 @@ int OSScapture::recordProc()
         pollitem.fd = mFd;
         pollitem.events = POLLIN;
 
-        int sret{poll(&pollitem, 1, 1000)};
-        if(sret < 0)
+        if(int pret{poll(&pollitem, 1, 1000)}; pret < 0)
         {
             if(errno == EINTR || errno == EAGAIN)
                 continue;
-            ERR("poll failed: %s\n", strerror(errno));
-            mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
+            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());
             break;
         }
-        else if(sret == 0)
+        else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */
         {
             WARN("poll timeout\n");
             continue;
@@ -504,8 +523,9 @@ int OSScapture::recordProc()
             ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
             if(amt < 0)
             {
-                ERR("read failed: %s\n", strerror(errno));
-                mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
+                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());
                 break;
             }
             mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
@@ -516,11 +536,11 @@ int OSScapture::recordProc()
 }
 
 
-void OSScapture::open(const char *name)
+void OSScapture::open(std::string_view name)
 {
     const char *devname{DefaultCapture.c_str()};
-    if(!name)
-        name = DefaultName;
+    if(name.empty())
+        name = GetDefaultName();
     else
     {
         if(CaptureDevices.empty())
@@ -532,14 +552,14 @@ void OSScapture::open(const char *name)
         );
         if(iter == CaptureDevices.cend())
             throw al::backend_exception{al::backend_error::NoDevice,
-                "Device name \"%s\" not found", name};
+                "Device name \"%.*s\" not found", al::sizei(name), name.data()};
         devname = iter->device_name.c_str();
     }
 
     mFd = ::open(devname, O_RDONLY);
     if(mFd == -1)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
-            strerror(errno)};
+            std::generic_category().message(errno).c_str()};
 
     int ossFormat{};
     switch(mDevice->FmtType)
@@ -566,13 +586,13 @@ void OSScapture::open(const char *name)
     uint frameSize{numChannels * mDevice->bytesFromFmt()};
     uint ossSpeed{mDevice->Frequency};
     /* according to the OSS spec, 16 bytes are the minimum */
-    uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
+    uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * 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", \
-        strerror(errno)};                                                     \
+        std::generic_category().message(errno).c_str()};                      \
 }
     CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
     CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
@@ -617,11 +637,11 @@ void OSScapture::stop()
     mThread.join();
 
     if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
-        ERR("Error resetting device: %s\n", strerror(errno));
+        ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str());
 }
 
-void OSScapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void OSScapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 uint OSScapture::availableSamples()
 { return static_cast<uint>(mRing->readSpace()); }
@@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory()
 
 bool OSSBackendFactory::init()
 {
-    if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
+    if(auto devopt = ConfigValueStr({}, "oss", "device"))
         DefaultPlayback = std::move(*devopt);
-    if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
+    if(auto capopt = ConfigValueStr({}, "oss", "capture"))
         DefaultCapture = std::move(*capopt);
 
     return true;

文件差异内容过多而无法显示
+ 416 - 204
libs/openal-soft/alc/backends/pipewire.cpp


+ 2 - 0
libs/openal-soft/alc/backends/pipewire.h

@@ -13,6 +13,8 @@ public:
 
     bool querySupport(BackendType type) override;
 
+    alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
+
     std::string probe(BackendType type) override;
 
     BackendPtr createBackend(DeviceBase *device, BackendType type) override;

+ 60 - 68
libs/openal-soft/alc/backends/portaudio.cpp

@@ -26,19 +26,23 @@
 #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>
+#include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
 
 
 namespace {
 
-constexpr char pa_device[] = "PortAudio Default";
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "PortAudio Default"sv; }
 
 
 #ifdef HAVE_DYNLOAD
@@ -77,15 +81,8 @@ struct PortPlayback final : public BackendBase {
 
     int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
         const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
-    static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
-        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
-        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
-    {
-        return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
-            framesPerBuffer, timeInfo, statusFlags);
-    }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -93,8 +90,6 @@ struct PortPlayback final : public BackendBase {
     PaStream *mStream{nullptr};
     PaStreamParameters mParams{};
     uint mUpdateSize{0u};
-
-    DEF_NEWDEL(PortPlayback)
 };
 
 PortPlayback::~PortPlayback()
@@ -115,16 +110,16 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f
 }
 
 
-void PortPlayback::open(const char *name)
+void PortPlayback::open(std::string_view name)
 {
-    if(!name)
-        name = pa_device;
-    else if(strcmp(name, pa_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     PaStreamParameters params{};
-    auto devidopt = ConfigValueInt(nullptr, "port", "device");
+    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);
@@ -155,19 +150,21 @@ void PortPlayback::open(const char *name)
         break;
     }
 
-retry_open:
+    static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
+        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
+    {
+        return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
+            framesPerBuffer, timeInfo, statusFlags);
+    };
     PaStream *stream{};
-    PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
-        paNoFlag, &PortPlayback::writeCallbackC, this)};
-    if(err != paNoError)
+    while(PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency,
+        mDevice->UpdateSize, paNoFlag, writeCallback, this)})
     {
-        if(params.sampleFormat == paFloat32)
-        {
-            params.sampleFormat = paInt16;
-            goto retry_open;
-        }
-        throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
-            Pa_GetErrorText(err)};
+        if(params.sampleFormat != paFloat32)
+            throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
+                Pa_GetErrorText(err)};
+        params.sampleFormat = paInt16;
     }
 
     Pa_CloseStream(mStream);
@@ -217,7 +214,7 @@ bool PortPlayback::reset()
 void PortPlayback::start()
 {
     const PaError err{Pa_StartStream(mStream)};
-    if(err == paNoError)
+    if(err != paNoError)
         throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
             Pa_GetErrorText(err)};
 }
@@ -235,27 +232,18 @@ struct PortCapture final : public BackendBase {
     ~PortCapture() override;
 
     int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
-        const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
-    static int readCallbackC(const void *inputBuffer, void *outputBuffer,
-        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
-        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
-    {
-        return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
-            framesPerBuffer, timeInfo, statusFlags);
-    }
+        const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept;
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     PaStream *mStream{nullptr};
-    PaStreamParameters mParams;
+    PaStreamParameters mParams{};
 
     RingBufferPtr mRing{nullptr};
-
-    DEF_NEWDEL(PortCapture)
 };
 
 PortCapture::~PortCapture()
@@ -268,28 +256,27 @@ PortCapture::~PortCapture()
 
 
 int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
-    const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
+    const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept
 {
-    mRing->write(inputBuffer, framesPerBuffer);
+    std::ignore = mRing->write(inputBuffer, framesPerBuffer);
     return 0;
 }
 
 
-void PortCapture::open(const char *name)
+void PortCapture::open(std::string_view name)
 {
-    if(!name)
-        name = pa_device;
-    else if(strcmp(name, pa_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
-    uint samples{mDevice->BufferSize};
-    samples = maxu(samples, 100 * mDevice->Frequency / 1000);
-    uint frame_size{mDevice->frameSizeFromFmt()};
+    const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
+    const uint frame_size{mDevice->frameSizeFromFmt()};
 
     mRing = RingBuffer::Create(samples, frame_size, false);
 
-    auto devidopt = ConfigValueInt(nullptr, "port", "capture");
+    auto devidopt = ConfigValueInt({}, "port", "capture");
     if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
     else mParams.device = Pa_GetDefaultOutputDevice();
     mParams.suggestedLatency = 0.0f;
@@ -319,8 +306,15 @@ void PortCapture::open(const char *name)
     }
     mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
 
+    static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer,
+        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
+    {
+        return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
+            framesPerBuffer, timeInfo, statusFlags);
+    };
     PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
-        paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
+        paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
     if(err != paNoError)
         throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
             Pa_GetErrorText(err)};
@@ -348,16 +342,14 @@ void PortCapture::stop()
 uint PortCapture::availableSamples()
 { return static_cast<uint>(mRing->readSpace()); }
 
-void PortCapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void PortCapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 } // namespace
 
 
 bool PortBackendFactory::init()
 {
-    PaError err;
-
 #ifdef HAVE_DYNLOAD
     if(!pa_handle)
     {
@@ -396,7 +388,8 @@ bool PortBackendFactory::init()
         LOAD_FUNC(Pa_GetStreamInfo);
 #undef LOAD_FUNC
 
-        if((err=Pa_Initialize()) != paNoError)
+        const PaError err{Pa_Initialize()};
+        if(err != paNoError)
         {
             ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
             CloseLib(pa_handle);
@@ -405,7 +398,8 @@ bool PortBackendFactory::init()
         }
     }
 #else
-    if((err=Pa_Initialize()) != paNoError)
+    const PaError err{Pa_Initialize()};
+    if(err != paNoError)
     {
         ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
         return false;
@@ -419,16 +413,14 @@ bool PortBackendFactory::querySupport(BackendType type)
 
 std::string PortBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
     case BackendType::Capture:
-        /* Includes null char. */
-        outnames.append(pa_device, sizeof(pa_device));
-        break;
+        /* Include null char. */
+        return std::string{GetDefaultName()} + '\0';
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)

文件差异内容过多而无法显示
+ 311 - 304
libs/openal-soft/alc/backends/pulseaudio.cpp


+ 2 - 0
libs/openal-soft/alc/backends/pulseaudio.h

@@ -9,6 +9,8 @@ public:
 
     bool querySupport(BackendType type) override;
 
+    alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
+
     std::string probe(BackendType type) override;
 
     BackendPtr createBackend(DeviceBase *device, BackendType type) override;

+ 41 - 21
libs/openal-soft/alc/backends/sdl2.cpp

@@ -26,13 +26,17 @@
 #include <cstdlib>
 #include <cstring>
 #include <string>
+#include <string_view>
 
 #include "almalloc.h"
 #include "alnumeric.h"
 #include "core/device.h"
 #include "core/logging.h"
 
-#include <SDL2/SDL.h>
+_Pragma("GCC diagnostic push")
+_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
+#include "SDL.h"
+_Pragma("GCC diagnostic pop")
 
 
 namespace {
@@ -43,17 +47,17 @@ namespace {
 #define DEVNAME_PREFIX ""
 #endif
 
-constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
+constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; }
+constexpr auto getDefaultDeviceName() noexcept -> std::string_view
+{ return DEVNAME_PREFIX "Default Device"; }
 
 struct Sdl2Backend final : public BackendBase {
     Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
     ~Sdl2Backend() override;
 
     void audioCallback(Uint8 *stream, int len) noexcept;
-    static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
-    { static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -65,8 +69,6 @@ struct Sdl2Backend final : public BackendBase {
     DevFmtChannels mFmtChans{};
     DevFmtType     mFmtType{};
     uint mUpdateSize{0u};
-
-    DEF_NEWDEL(Sdl2Backend)
 };
 
 Sdl2Backend::~Sdl2Backend()
@@ -83,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
     mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
 }
 
-void Sdl2Backend::open(const char *name)
+void Sdl2Backend::open(std::string_view name)
 {
     SDL_AudioSpec want{}, have{};
 
@@ -99,24 +101,39 @@ void Sdl2Backend::open(const char *name)
     case DevFmtFloat: want.format = AUDIO_F32; break;
     }
     want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
-    want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
-    want.callback = &Sdl2Backend::audioCallbackC;
+    want.samples = static_cast<Uint16>(std::min(mDevice->UpdateSize, 8192u));
+    want.callback = [](void *ptr, Uint8 *stream, int len) noexcept
+    { return static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); };
     want.userdata = this;
 
     /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
      * necessarily the first in the list.
      */
+    const auto defaultDeviceName = getDefaultDeviceName();
     SDL_AudioDeviceID devid;
-    if(!name || strcmp(name, defaultDeviceName) == 0)
+    if(name.empty() || name == defaultDeviceName)
+    {
+        name = defaultDeviceName;
         devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
+    }
     else
     {
-        const size_t prefix_len = strlen(DEVNAME_PREFIX);
-        if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
-            devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
+        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
-            devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
+        {
+            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()};
@@ -158,7 +175,7 @@ void Sdl2Backend::open(const char *name)
     mFmtType = devtype;
     mUpdateSize = have.samples;
 
-    mDevice->DeviceName = name ? name : defaultDeviceName;
+    mDevice->DeviceName = name;
 }
 
 bool Sdl2Backend::reset()
@@ -202,13 +219,16 @@ std::string SDL2BackendFactory::probe(BackendType type)
     int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
 
     /* Includes null char. */
-    outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
+    outnames += getDefaultDeviceName();
+    outnames += '\0';
     for(int i{0};i < num_devices;++i)
     {
-        std::string name{DEVNAME_PREFIX};
-        name += SDL_GetAudioDeviceName(i, SDL_FALSE);
-        if(!name.empty())
-            outnames.append(name.c_str(), name.length()+1);
+        outnames += getDevicePrefix();
+        if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE))
+            outnames += name;
+        else
+            outnames += "Unknown Device Name #"+std::to_string(i);
+        outnames += '\0';
     }
     return outnames;
 }

+ 131 - 122
libs/openal-soft/alc/backends/sndio.cpp

@@ -22,31 +22,35 @@
 
 #include "sndio.h"
 
+#include <cinttypes>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
 #include <poll.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
+#include <system_error>
 #include <thread>
-#include <functional>
+#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 "threads.h"
-#include "vector.h"
 
-#include <sndio.h>
+#include <sndio.h> /* NOLINT(*-duplicate-include) Not the same header. */
 
 
 namespace {
 
-static const char sndio_device[] = "SndIO Default";
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; }
 
 struct SioPar : public sio_par {
-    SioPar() { sio_initpar(this); }
+    SioPar() : sio_par{} { sio_initpar(this); }
 
     void clear() { sio_initpar(this); }
 };
@@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase {
     sio_hdl *mSndHandle{nullptr};
     uint mFrameStep{};
 
-    al::vector<al::byte> mBuffer;
+    std::vector<std::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(SndioPlayback)
 };
 
 SndioPlayback::~SndioPlayback()
@@ -86,21 +88,21 @@ int SndioPlayback::mixerProc()
     const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
 
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
     {
-        al::span<al::byte> buffer{mBuffer};
+        al::span<std::byte> buffer{mBuffer};
 
         mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
             frameStep);
         while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
         {
             size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
-            if(wrote == 0)
+            if(wrote > buffer.size() || wrote == 0)
             {
-                ERR("sio_write failed\n");
+                ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
                 mDevice->handleDisconnect("Failed to write playback samples");
                 break;
             }
@@ -112,13 +114,13 @@ int SndioPlayback::mixerProc()
 }
 
 
-void SndioPlayback::open(const char *name)
+void SndioPlayback::open(std::string_view name)
 {
-    if(!name)
-        name = sndio_device;
-    else if(strcmp(name, sndio_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
     if(!sndHandle)
@@ -136,72 +138,75 @@ bool SndioPlayback::reset()
     SioPar par;
 
     auto tryfmt = mDevice->FmtType;
-retry_params:
-    switch(tryfmt)
+    while(true)
     {
-    case DevFmtByte:
-        par.bits = 8;
-        par.sig = 1;
-        break;
-    case DevFmtUByte:
-        par.bits = 8;
-        par.sig = 0;
-        break;
-    case DevFmtShort:
-        par.bits = 16;
-        par.sig = 1;
-        break;
-    case DevFmtUShort:
-        par.bits = 16;
-        par.sig = 0;
-        break;
-    case DevFmtFloat:
-    case DevFmtInt:
-        par.bits = 32;
-        par.sig = 1;
-        break;
-    case DevFmtUInt:
-        par.bits = 32;
-        par.sig = 0;
-        break;
-    }
-    par.bps = SIO_BPS(par.bits);
-    par.le = SIO_LE_NATIVE;
-    par.msb = 1;
-
-    par.rate = mDevice->Frequency;
-    par.pchan = mDevice->channelsFromFmt();
-
-    par.round = mDevice->UpdateSize;
-    par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
-    if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
+        switch(tryfmt)
+        {
+        case DevFmtByte:
+            par.bits = 8;
+            par.sig = 1;
+            break;
+        case DevFmtUByte:
+            par.bits = 8;
+            par.sig = 0;
+            break;
+        case DevFmtShort:
+            par.bits = 16;
+            par.sig = 1;
+            break;
+        case DevFmtUShort:
+            par.bits = 16;
+            par.sig = 0;
+            break;
+        case DevFmtFloat:
+        case DevFmtInt:
+            par.bits = 32;
+            par.sig = 1;
+            break;
+        case DevFmtUInt:
+            par.bits = 32;
+            par.sig = 0;
+            break;
+        }
+        par.bps = SIO_BPS(par.bits);
+        par.le = SIO_LE_NATIVE;
+        par.msb = 1;
+
+        par.rate = mDevice->Frequency;
+        par.pchan = mDevice->channelsFromFmt();
+
+        par.round = mDevice->UpdateSize;
+        par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
+        if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
+
+        try {
+            if(!sio_setpar(mSndHandle, &par))
+                throw al::backend_exception{al::backend_error::DeviceError,
+                    "Failed to set device parameters"};
+
+            par.clear();
+            if(!sio_getpar(mSndHandle, &par))
+                throw al::backend_exception{al::backend_error::DeviceError,
+                    "Failed to get device parameters"};
+
+            if(par.bps > 1 && par.le != SIO_LE_NATIVE)
+                throw al::backend_exception{al::backend_error::DeviceError,
+                    "%s-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};
+            if(par.pchan < 1)
+                throw al::backend_exception{al::backend_error::DeviceError,
+                    "No playback channels on device"};
 
-    try {
-        if(!sio_setpar(mSndHandle, &par))
-            throw al::backend_exception{al::backend_error::DeviceError,
-                "Failed to set device parameters"};
-
-        par.clear();
-        if(!sio_getpar(mSndHandle, &par))
-            throw al::backend_exception{al::backend_error::DeviceError,
-                "Failed to get device parameters"};
-
-        if(par.bps > 1 && par.le != SIO_LE_NATIVE)
-            throw al::backend_exception{al::backend_error::DeviceError,
-                "%s-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};
-        if(par.pchan < 1)
-            throw al::backend_exception{al::backend_error::DeviceError,
-                "No playback channels on device"};
-    }
-    catch(al::backend_exception &e) {
-        if(tryfmt == DevFmtShort)
-            throw;
-        par.clear();
-        tryfmt = DevFmtShort;
-        goto retry_params;
+            break;
+        }
+        catch(al::backend_exception &e) {
+            if(tryfmt == DevFmtShort)
+                throw;
+            par.clear();
+            tryfmt = DevFmtShort;
+        }
     }
 
     if(par.bps == 1)
@@ -229,11 +234,11 @@ retry_params:
     mDevice->UpdateSize = par.round;
     mDevice->BufferSize = par.bufsz + par.round;
 
-    mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
+    mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps);
     if(par.sig == 1)
-        std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
+        std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
     else if(par.bits == 8)
-        std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
+        std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80));
     else if(par.bits == 16)
         std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
     else if(par.bits == 32)
@@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase {
 
     int recordProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     sio_hdl *mSndHandle{nullptr};
@@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(SndioCapture)
 };
 
 SndioCapture::~SndioCapture()
@@ -306,7 +309,7 @@ SndioCapture::~SndioCapture()
 int SndioCapture::recordProc()
 {
     SetRTPriority();
-    althrd_setname(RECORD_THREAD_NAME);
+    althrd_setname(GetRecordThreadName());
 
     const uint frameSize{mDevice->frameSizeFromFmt()};
 
@@ -317,29 +320,30 @@ int SndioCapture::recordProc()
         return 1;
     }
 
-    auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
+    auto fds = std::vector<pollfd>(static_cast<uint>(nfds_pre));
 
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
     {
         /* Wait until there's some samples to read. */
-        const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
+        const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)};
         if(nfds <= 0)
         {
             mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
             break;
         }
-        int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
+        int pollres{::poll(fds.data(), fds.size(), 2000)};
         if(pollres < 0)
         {
             if(errno == EINTR) continue;
-            mDevice->handleDisconnect("Poll error: %s", strerror(errno));
+            mDevice->handleDisconnect("Poll error: %s",
+                std::generic_category().message(errno).c_str());
             break;
         }
         if(pollres == 0)
             continue;
 
-        const int revents{sio_revents(mSndHandle, fds.get())};
+        const int revents{sio_revents(mSndHandle, fds.data())};
         if((revents&POLLHUP))
         {
             mDevice->handleDisconnect("Got POLLHUP from poll events");
@@ -349,11 +353,18 @@ int SndioCapture::recordProc()
             continue;
 
         auto data = mRing->getWriteVector();
-        al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
+        al::span<std::byte> buffer{data.first.buf, data.first.len*frameSize};
         while(!buffer.empty())
         {
             size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
-            if(got == 0) break;
+            if(got == 0)
+                break;
+            if(got > buffer.size())
+            {
+                ERR("sio_read failed: 0x%" PRIx64 "\n", got);
+                mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
+                break;
+            }
 
             mRing->writeAdvance(got / frameSize);
             buffer = buffer.subspan(got);
@@ -366,8 +377,8 @@ int SndioCapture::recordProc()
         if(buffer.empty())
         {
             /* Got samples to read, but no place to store it. Drop it. */
-            static char junk[4096];
-            sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
+            static std::array<char,4096> junk;
+            sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize));
         }
     }
 
@@ -375,13 +386,13 @@ int SndioCapture::recordProc()
 }
 
 
-void SndioCapture::open(const char *name)
+void SndioCapture::open(std::string_view name)
 {
-    if(!name)
-        name = sndio_device;
-    else if(strcmp(name, sndio_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     mSndHandle = sio_open(nullptr, SIO_REC, true);
     if(mSndHandle == nullptr)
@@ -424,12 +435,12 @@ void SndioCapture::open(const char *name)
     par.rchan = mDevice->channelsFromFmt();
     par.rate = mDevice->Frequency;
 
-    par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
-    par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
+    par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u);
+    par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u);
 
     if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
         throw al::backend_exception{al::backend_error::DeviceError,
-            "Failed to set device praameters"};
+            "Failed to set device parameters"};
 
     if(par.bps > 1 && par.le != SIO_LE_NATIVE)
         throw al::backend_exception{al::backend_error::DeviceError,
@@ -454,7 +465,7 @@ void SndioCapture::open(const char *name)
             DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
             mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
 
-    mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
+    mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false);
     mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
     mDevice->UpdateSize = par.round;
 
@@ -489,8 +500,8 @@ void SndioCapture::stop()
         ERR("Error stopping device\n");
 }
 
-void SndioCapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void SndioCapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 uint SndioCapture::availableSamples()
 { return static_cast<uint>(mRing->readSpace()); }
@@ -511,16 +522,14 @@ bool SndIOBackendFactory::querySupport(BackendType type)
 
 std::string SndIOBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
     case BackendType::Capture:
-        /* Includes null char. */
-        outnames.append(sndio_device, sizeof(sndio_device));
-        break;
+        /* Include null char. */
+        return std::string{GetDefaultName()} + '\0';
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)

+ 28 - 33
libs/openal-soft/alc/backends/solaris.cpp

@@ -35,24 +35,26 @@
 #include <poll.h>
 #include <math.h>
 #include <string.h>
+#include <vector>
 
 #include <thread>
 #include <functional>
 
-#include "albyte.h"
 #include "alc/alconfig.h"
+#include "alstring.h"
+#include "althrd_setname.h"
 #include "core/device.h"
 #include "core/helpers.h"
 #include "core/logging.h"
-#include "threads.h"
-#include "vector.h"
 
 #include <sys/audioio.h>
 
 
 namespace {
 
-constexpr char solaris_device[] = "Solaris Default";
+using namespace std::string_view_literals;
+
+[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; }
 
 std::string solaris_driver{"/dev/audio"};
 
@@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase {
     int mFd{-1};
 
     uint mFrameStep{};
-    al::vector<al::byte> mBuffer;
+    std::vector<std::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(SolarisBackend)
 };
 
 SolarisBackend::~SolarisBackend()
@@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend()
 int SolarisBackend::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const size_t frame_step{mDevice->channelsFromFmt()};
-    const uint frame_size{mDevice->frameSizeFromFmt()};
+    const size_t frame_size{mDevice->frameSizeFromFmt()};
 
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
@@ -116,12 +116,12 @@ int SolarisBackend::mixerProc()
             continue;
         }
 
-        al::byte *write_ptr{mBuffer.data()};
-        size_t to_write{mBuffer.size()};
-        mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
-        while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
+        al::span<std::byte> buffer{mBuffer};
+        mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size()/frame_size),
+            frame_step);
+        while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
         {
-            ssize_t wrote{write(mFd, write_ptr, to_write)};
+            ssize_t wrote{write(mFd, buffer.data(), buffer.size())};
             if(wrote < 0)
             {
                 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
@@ -131,8 +131,7 @@ int SolarisBackend::mixerProc()
                 break;
             }
 
-            to_write -= static_cast<size_t>(wrote);
-            write_ptr += wrote;
+            buffer = buffer.subspan(static_cast<size_t>(wrote));
         }
     }
 
@@ -140,13 +139,13 @@ int SolarisBackend::mixerProc()
 }
 
 
-void SolarisBackend::open(const char *name)
+void SolarisBackend::open(std::string_view name)
 {
-    if(!name)
-        name = solaris_device;
-    else if(strcmp(name, solaris_device) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     int fd{::open(solaris_driver.c_str(), O_WRONLY)};
     if(fd == -1)
@@ -231,7 +230,7 @@ bool SolarisBackend::reset()
     setDefaultChannelOrder();
 
     mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
-    std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
+    std::fill(mBuffer.begin(), mBuffer.end(), std::byte{});
 
     return true;
 }
@@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory()
 
 bool SolarisBackendFactory::init()
 {
-    if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
+    if(auto devopt = ConfigValueStr({}, "solaris", "device"))
         solaris_driver = std::move(*devopt);
     return true;
 }
@@ -278,21 +277,17 @@ bool SolarisBackendFactory::querySupport(BackendType type)
 
 std::string SolarisBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
-    {
-        struct stat buf;
-        if(stat(solaris_driver.c_str(), &buf) == 0)
-            outnames.append(solaris_device, sizeof(solaris_device));
-    }
-    break;
+        if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0)
+            return std::string{GetDefaultName()} + '\0';
+        break;
 
     case BackendType::Capture:
         break;
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)

文件差异内容过多而无法显示
+ 595 - 189
libs/openal-soft/alc/backends/wasapi.cpp


+ 2 - 0
libs/openal-soft/alc/backends/wasapi.h

@@ -9,6 +9,8 @@ public:
 
     bool querySupport(BackendType type) override;
 
+    alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override;
+
     std::string probe(BackendType type) override;
 
     BackendPtr createBackend(DeviceBase *device, BackendType type) override;

+ 86 - 84
libs/openal-soft/alc/backends/wave.cpp

@@ -31,24 +31,26 @@
 #include <cstring>
 #include <exception>
 #include <functional>
+#include <system_error>
 #include <thread>
+#include <vector>
 
 #include "albit.h"
-#include "albyte.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 "opthelpers.h"
 #include "strutils.h"
-#include "threads.h"
-#include "vector.h"
 
 
 namespace {
 
+using namespace std::string_view_literals;
 using std::chrono::seconds;
 using std::chrono::milliseconds;
 using std::chrono::nanoseconds;
@@ -56,38 +58,43 @@ using std::chrono::nanoseconds;
 using ubyte = unsigned char;
 using ushort = unsigned short;
 
-constexpr char waveDevice[] = "Wave File Writer";
+struct FileDeleter {
+    void operator()(gsl::owner<FILE*> f) { fclose(f); }
+};
+using FilePtr = std::unique_ptr<FILE,FileDeleter>;
+
+[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; }
 
-constexpr ubyte SUBTYPE_PCM[]{
+constexpr std::array<ubyte,16> SUBTYPE_PCM{{
     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
     0x00, 0x38, 0x9b, 0x71
-};
-constexpr ubyte SUBTYPE_FLOAT[]{
+}};
+constexpr std::array<ubyte,16> SUBTYPE_FLOAT{{
     0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
     0x00, 0x38, 0x9b, 0x71
-};
+}};
 
-constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
+constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_PCM{{
     0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
     0xca, 0x00, 0x00, 0x00
-};
+}};
 
-constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
+constexpr std::array<ubyte,16> SUBTYPE_BFORMAT_FLOAT{{
     0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
     0xca, 0x00, 0x00, 0x00
-};
+}};
 
 void fwrite16le(ushort val, FILE *f)
 {
-    ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
-    fwrite(data, 1, 2, f);
+    std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff)};
+    fwrite(data.data(), 1, data.size(), f);
 }
 
 void fwrite32le(uint val, FILE *f)
 {
-    ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
-        static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
-    fwrite(data, 1, 4, f);
+    std::array data{static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
+        static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff)};
+    fwrite(data.data(), 1, data.size(), f);
 }
 
 
@@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
 
-    FILE *mFile{nullptr};
+    FilePtr mFile{nullptr};
     long mDataStart{-1};
 
-    al::vector<al::byte> mBuffer;
+    std::vector<std::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(WaveBackend)
 };
 
-WaveBackend::~WaveBackend()
-{
-    if(mFile)
-        fclose(mFile);
-    mFile = nullptr;
-}
+WaveBackend::~WaveBackend() = default;
 
 int WaveBackend::mixerProc()
 {
     const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
 
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     const size_t frameStep{mDevice->channelsFromFmt()};
     const size_t frameSize{mDevice->frameSizeFromFmt()};
@@ -155,13 +155,13 @@ int WaveBackend::mixerProc()
 
                 if(bytesize == 2)
                 {
-                    const size_t len{mBuffer.size() & ~size_t{1}};
+                    const size_t len{mBuffer.size() & ~1_uz};
                     for(size_t i{0};i < len;i+=2)
                         std::swap(mBuffer[i], mBuffer[i+1]);
                 }
                 else if(bytesize == 4)
                 {
-                    const size_t len{mBuffer.size() & ~size_t{3}};
+                    const size_t len{mBuffer.size() & ~3_uz};
                     for(size_t i{0};i < len;i+=4)
                     {
                         std::swap(mBuffer[i  ], mBuffer[i+3]);
@@ -170,8 +170,8 @@ int WaveBackend::mixerProc()
                 }
             }
 
-            const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
-            if(fs < mDevice->UpdateSize || ferror(mFile))
+            const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())};
+            if(fs < mDevice->UpdateSize || ferror(mFile.get()))
             {
                 ERR("Error writing to file\n");
                 mDevice->handleDisconnect("Failed to write playback samples");
@@ -195,32 +195,32 @@ int WaveBackend::mixerProc()
     return 0;
 }
 
-void WaveBackend::open(const char *name)
+void WaveBackend::open(std::string_view name)
 {
-    auto fname = ConfigValueStr(nullptr, "wave", "file");
+    auto fname = ConfigValueStr({}, "wave", "file");
     if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
         "No wave output filename"};
 
-    if(!name)
-        name = waveDevice;
-    else if(strcmp(name, waveDevice) != 0)
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            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()};
 
     /* There's only one "device", so if it's already open, we're done. */
     if(mFile) return;
 
 #ifdef _WIN32
     {
-        std::wstring wname{utf8_to_wstr(fname->c_str())};
-        mFile = _wfopen(wname.c_str(), L"wb");
+        std::wstring wname{utf8_to_wstr(fname.value())};
+        mFile = FilePtr{_wfopen(wname.c_str(), L"wb")};
     }
 #else
-    mFile = fopen(fname->c_str(), "wb");
+    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(), strerror(errno)};
+            fname->c_str(), std::generic_category().message(errno).c_str()};
 
     mDevice->DeviceName = name;
 }
@@ -229,12 +229,11 @@ bool WaveBackend::reset()
 {
     uint channels{0}, bytes{0}, chanmask{0};
     bool isbformat{false};
-    size_t val;
 
-    fseek(mFile, 0, SEEK_SET);
-    clearerr(mFile);
+    fseek(mFile.get(), 0, SEEK_SET);
+    clearerr(mFile.get());
 
-    if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
+    if(GetConfigValueBool({}, "wave", "bformat", false))
     {
         mDevice->FmtChans = DevFmtAmbi3D;
         mDevice->mAmbiOrder = 1;
@@ -265,9 +264,15 @@ 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 DevFmtX714:
+        chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000
+            | 0x8000 | 0x20000;
+        break;
+    /* NOTE: Same as 7.1. */
+    case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
     case DevFmtAmbi3D:
         /* .amb output requires FuMa */
-        mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
+        mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u);
         mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
         mDevice->mAmbiScale = DevAmbiScaling::FuMa;
         isbformat = true;
@@ -277,49 +282,48 @@ bool WaveBackend::reset()
     bytes = mDevice->bytesFromFmt();
     channels = mDevice->channelsFromFmt();
 
-    rewind(mFile);
+    rewind(mFile.get());
 
-    fputs("RIFF", mFile);
-    fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
+    fputs("RIFF", mFile.get());
+    fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close
 
-    fputs("WAVE", mFile);
+    fputs("WAVE", mFile.get());
 
-    fputs("fmt ", mFile);
-    fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
+    fputs("fmt ", mFile.get());
+    fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE
 
     // 16-bit val, format type id (extensible: 0xFFFE)
-    fwrite16le(0xFFFE, mFile);
+    fwrite16le(0xFFFE, mFile.get());
     // 16-bit val, channel count
-    fwrite16le(static_cast<ushort>(channels), mFile);
+    fwrite16le(static_cast<ushort>(channels), mFile.get());
     // 32-bit val, frequency
-    fwrite32le(mDevice->Frequency, mFile);
+    fwrite32le(mDevice->Frequency, mFile.get());
     // 32-bit val, bytes per second
-    fwrite32le(mDevice->Frequency * channels * bytes, mFile);
+    fwrite32le(mDevice->Frequency * channels * bytes, mFile.get());
     // 16-bit val, frame size
-    fwrite16le(static_cast<ushort>(channels * bytes), mFile);
+    fwrite16le(static_cast<ushort>(channels * bytes), mFile.get());
     // 16-bit val, bits per sample
-    fwrite16le(static_cast<ushort>(bytes * 8), mFile);
+    fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
     // 16-bit val, extra byte count
-    fwrite16le(22, mFile);
+    fwrite16le(22, mFile.get());
     // 16-bit val, valid bits per sample
-    fwrite16le(static_cast<ushort>(bytes * 8), mFile);
+    fwrite16le(static_cast<ushort>(bytes * 8), mFile.get());
     // 32-bit val, channel mask
-    fwrite32le(chanmask, mFile);
+    fwrite32le(chanmask, mFile.get());
     // 16 byte GUID, sub-type format
-    val = fwrite((mDevice->FmtType == DevFmtFloat) ?
-        (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
-        (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
-    (void)val;
+    std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ?
+        (isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) :
+        (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get());
 
-    fputs("data", mFile);
-    fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
+    fputs("data", mFile.get());
+    fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close
 
-    if(ferror(mFile))
+    if(ferror(mFile.get()))
     {
-        ERR("Error writing header: %s\n", strerror(errno));
+        ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str());
         return false;
     }
-    mDataStart = ftell(mFile);
+    mDataStart = ftell(mFile.get());
 
     setDefaultWFXChannelOrder();
 
@@ -331,7 +335,7 @@ bool WaveBackend::reset()
 
 void WaveBackend::start()
 {
-    if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
+    if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0)
         WARN("Failed to seek on output file\n");
     try {
         mKillNow.store(false, std::memory_order_release);
@@ -351,14 +355,14 @@ void WaveBackend::stop()
 
     if(mDataStart > 0)
     {
-        long size{ftell(mFile)};
+        long size{ftell(mFile.get())};
         if(size > 0)
         {
             long dataLen{size - mDataStart};
-            if(fseek(mFile, 4, SEEK_SET) == 0)
-                fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
-            if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
-                fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
+            if(fseek(mFile.get(), 4, SEEK_SET) == 0)
+                fwrite32le(static_cast<uint>(size-8), mFile.get()); // 'WAVE' header len
+            if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0)
+                fwrite32le(static_cast<uint>(dataLen), mFile.get()); // 'data' header len
         }
     }
 }
@@ -374,17 +378,15 @@ bool WaveBackendFactory::querySupport(BackendType type)
 
 std::string WaveBackendFactory::probe(BackendType type)
 {
-    std::string outnames;
     switch(type)
     {
     case BackendType::Playback:
-        /* Includes null char. */
-        outnames.append(waveDevice, sizeof(waveDevice));
-        break;
+        /* Include null char. */
+        return std::string{GetDeviceName()} + '\0';
     case BackendType::Capture:
         break;
     }
-    return outnames;
+    return std::string{};
 }
 
 BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)

+ 42 - 51
libs/openal-soft/alc/backends/winmm.cpp

@@ -22,8 +22,8 @@
 
 #include "winmm.h"
 
-#include <stdlib.h>
-#include <stdio.h>
+#include <cstdlib>
+#include <cstdio>
 #include <memory.h>
 
 #include <windows.h>
@@ -39,12 +39,15 @@
 #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 "ringbuffer.h"
 #include "strutils.h"
-#include "threads.h"
+#include "vector.h"
 
 #ifndef WAVE_FORMAT_IEEE_FLOAT
 #define WAVE_FORMAT_IEEE_FLOAT  0x0003
@@ -55,13 +58,13 @@ namespace {
 #define DEVNAME_HEAD "OpenAL Soft on "
 
 
-al::vector<std::string> PlaybackDevices;
-al::vector<std::string> CaptureDevices;
+std::vector<std::string> PlaybackDevices;
+std::vector<std::string> CaptureDevices;
 
-bool checkName(const al::vector<std::string> &list, const std::string &name)
+bool checkName(const std::vector<std::string> &list, const std::string &name)
 { return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
 
-void ProbePlaybackDevices(void)
+void ProbePlaybackDevices()
 {
     PlaybackDevices.clear();
 
@@ -92,7 +95,7 @@ void ProbePlaybackDevices(void)
     }
 }
 
-void ProbeCaptureDevices(void)
+void ProbeCaptureDevices()
 {
     CaptureDevices.clear();
 
@@ -134,7 +137,7 @@ struct WinMMPlayback final : public BackendBase {
 
     int mixerProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -143,6 +146,7 @@ struct WinMMPlayback final : public BackendBase {
     al::semaphore mSem;
     uint mIdx{0u};
     std::array<WAVEHDR,4> mWaveBuffer{};
+    al::vector<char,16> mBuffer;
 
     HWAVEOUT mOutHdl{nullptr};
 
@@ -150,8 +154,6 @@ struct WinMMPlayback final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(WinMMPlayback)
 };
 
 WinMMPlayback::~WinMMPlayback()
@@ -159,14 +161,11 @@ WinMMPlayback::~WinMMPlayback()
     if(mOutHdl)
         waveOutClose(mOutHdl);
     mOutHdl = nullptr;
-
-    al_free(mWaveBuffer[0].lpData);
-    std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
 }
 
 /* WinMMPlayback::waveOutProc
  *
- * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
+ * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is
  * completed and returns to the application (for more data)
  */
 void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@@ -179,7 +178,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT
 FORCE_ALIGN int WinMMPlayback::mixerProc()
 {
     SetRTPriority();
-    althrd_setname(MIXER_THREAD_NAME);
+    althrd_setname(GetMixerThreadName());
 
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
@@ -207,18 +206,18 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
 }
 
 
-void WinMMPlayback::open(const char *name)
+void WinMMPlayback::open(std::string_view name)
 {
     if(PlaybackDevices.empty())
         ProbePlaybackDevices();
 
     // Find the Device ID matching the deviceName if valid
-    auto iter = name ?
+    auto iter = !name.empty() ?
         std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
         PlaybackDevices.cbegin();
     if(iter == PlaybackDevices.cend())
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            name};
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
+            al::sizei(name), name.data()};
     auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
 
     DevFmtType fmttype{mDevice->FmtType};
@@ -301,29 +300,22 @@ bool WinMMPlayback::reset()
         return false;
     }
 
-    uint chanmask{};
     if(mFormat.nChannels >= 2)
-    {
         mDevice->FmtChans = DevFmtStereo;
-        chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
-    }
     else if(mFormat.nChannels == 1)
-    {
         mDevice->FmtChans = DevFmtMono;
-        chanmask = SPEAKER_FRONT_CENTER;
-    }
     else
     {
         ERR("Unhandled channel count: %d\n", mFormat.nChannels);
         return false;
     }
-    setChannelOrderFromWFXMask(chanmask);
+    setDefaultWFXChannelOrder();
 
-    uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
+    const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
 
-    al_free(mWaveBuffer[0].lpData);
+    decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
     mWaveBuffer[0] = WAVEHDR{};
-    mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
+    mWaveBuffer[0].lpData = mBuffer.data();
     mWaveBuffer[0].dwBufferLength = BufferSize;
     for(size_t i{1};i < mWaveBuffer.size();i++)
     {
@@ -376,16 +368,17 @@ struct WinMMCapture final : public BackendBase {
 
     int captureProc();
 
-    void open(const char *name) override;
+    void open(std::string_view name) override;
     void start() override;
     void stop() override;
-    void captureSamples(al::byte *buffer, uint samples) override;
+    void captureSamples(std::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
     std::atomic<uint> mReadable{0u};
     al::semaphore mSem;
     uint mIdx{0};
     std::array<WAVEHDR,4> mWaveBuffer{};
+    al::vector<char,16> mBuffer;
 
     HWAVEIN mInHdl{nullptr};
 
@@ -395,8 +388,6 @@ struct WinMMCapture final : public BackendBase {
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
-
-    DEF_NEWDEL(WinMMCapture)
 };
 
 WinMMCapture::~WinMMCapture()
@@ -405,14 +396,11 @@ WinMMCapture::~WinMMCapture()
     if(mInHdl)
         waveInClose(mInHdl);
     mInHdl = nullptr;
-
-    al_free(mWaveBuffer[0].lpData);
-    std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
 }
 
 /* WinMMCapture::waveInProc
  *
- * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
+ * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is
  * completed and returns to the application (with more data).
  */
 void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
@@ -424,7 +412,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR)
 
 int WinMMCapture::captureProc()
 {
-    althrd_setname(RECORD_THREAD_NAME);
+    althrd_setname(GetRecordThreadName());
 
     while(!mKillNow.load(std::memory_order_acquire) &&
           mDevice->Connected.load(std::memory_order_acquire))
@@ -441,7 +429,8 @@ int WinMMCapture::captureProc()
             WAVEHDR &waveHdr = mWaveBuffer[widx];
             widx = (widx+1) % mWaveBuffer.size();
 
-            mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
+            std::ignore = mRing->write(waveHdr.lpData,
+                waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
             mReadable.fetch_sub(1, std::memory_order_acq_rel);
             waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
         } while(--todo);
@@ -452,18 +441,18 @@ int WinMMCapture::captureProc()
 }
 
 
-void WinMMCapture::open(const char *name)
+void WinMMCapture::open(std::string_view name)
 {
     if(CaptureDevices.empty())
         ProbeCaptureDevices();
 
     // Find the Device ID matching the deviceName if valid
-    auto iter = name ?
+    auto iter = !name.empty() ?
         std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
         CaptureDevices.cbegin();
     if(iter == CaptureDevices.cend())
-        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
-            name};
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found",
+            al::sizei(name), name.data()};
     auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
 
     switch(mDevice->FmtChans)
@@ -476,6 +465,8 @@ void WinMMCapture::open(const char *name)
     case DevFmtX51:
     case DevFmtX61:
     case DevFmtX71:
+    case DevFmtX714:
+    case DevFmtX3D71:
     case DevFmtAmbi3D:
         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
             DevFmtChannelsString(mDevice->FmtChans)};
@@ -518,14 +509,14 @@ void WinMMCapture::open(const char *name)
 
     // Allocate circular memory buffer for the captured audio
     // Make sure circular buffer is at least 100ms in size
-    uint CapturedDataSize{mDevice->BufferSize};
-    CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
+    const auto CapturedDataSize = std::max<size_t>(mDevice->BufferSize,
+        BufferSize*mWaveBuffer.size());
 
     mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
 
-    al_free(mWaveBuffer[0].lpData);
+    decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer);
     mWaveBuffer[0] = WAVEHDR{};
-    mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
+    mWaveBuffer[0].lpData = mBuffer.data();
     mWaveBuffer[0].dwBufferLength = BufferSize;
     for(size_t i{1};i < mWaveBuffer.size();++i)
     {
@@ -576,8 +567,8 @@ void WinMMCapture::stop()
     mIdx = 0;
 }
 
-void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
-{ mRing->read(buffer, samples); }
+void WinMMCapture::captureSamples(std::byte *buffer, uint samples)
+{ std::ignore = mRing->read(buffer, samples); }
 
 uint WinMMCapture::availableSamples()
 { return static_cast<uint>(mRing->readSpace()); }

+ 545 - 785
libs/openal-soft/alc/context.cpp

@@ -4,21 +4,28 @@
 #include "context.h"
 
 #include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstring>
 #include <functional>
 #include <limits>
 #include <numeric>
-#include <stddef.h>
 #include <stdexcept>
+#include <string_view>
+#include <utility>
 
 #include "AL/efx.h"
 
 #include "al/auxeffectslot.h"
+#include "al/debug.h"
 #include "al/source.h"
 #include "al/effect.h"
 #include "al/event.h"
 #include "al/listener.h"
 #include "albit.h"
 #include "alc/alu.h"
+#include "alc/backends/base.h"
+#include "alspan.h"
 #include "core/async_event.h"
 #include "core/device.h"
 #include "core/effectslot.h"
@@ -26,72 +33,79 @@
 #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 <cassert>
-#include <cstring>
-
 #include "alstring.h"
-#include "al/eax_exception.h"
-#include "al/eax_globals.h"
+#include "al/eax/globals.h"
 #endif // ALSOFT_EAX
 
 namespace {
 
-using namespace std::placeholders;
-
+using namespace std::string_view_literals;
 using voidp = void*;
 
 /* Default context extensions */
-constexpr ALchar alExtList[] =
-    "AL_EXT_ALAW "
-    "AL_EXT_BFORMAT "
-    "AL_EXT_DOUBLE "
-    "AL_EXT_EXPONENT_DISTANCE "
-    "AL_EXT_FLOAT32 "
-    "AL_EXT_IMA4 "
-    "AL_EXT_LINEAR_DISTANCE "
-    "AL_EXT_MCFORMATS "
-    "AL_EXT_MULAW "
-    "AL_EXT_MULAW_BFORMAT "
-    "AL_EXT_MULAW_MCFORMATS "
-    "AL_EXT_OFFSET "
-    "AL_EXT_source_distance_model "
-    "AL_EXT_SOURCE_RADIUS "
-    "AL_EXT_STEREO_ANGLES "
-    "AL_LOKI_quadriphonic "
-    "AL_SOFT_bformat_ex "
-    "AL_SOFTX_bformat_hoa "
-    "AL_SOFT_block_alignment "
-    "AL_SOFT_callback_buffer "
-    "AL_SOFTX_convolution_reverb "
-    "AL_SOFT_deferred_updates "
-    "AL_SOFT_direct_channels "
-    "AL_SOFT_direct_channels_remix "
-    "AL_SOFT_effect_target "
-    "AL_SOFT_events "
-    "AL_SOFT_gain_clamp_ex "
-    "AL_SOFTX_hold_on_disconnect "
-    "AL_SOFT_loop_points "
-    "AL_SOFTX_map_buffer "
-    "AL_SOFT_MSADPCM "
-    "AL_SOFT_source_latency "
-    "AL_SOFT_source_length "
-    "AL_SOFT_source_resampler "
-    "AL_SOFT_source_spatialize "
-    "AL_SOFT_UHJ";
+std::vector<std::string_view> getContextExtensions() noexcept
+{
+    return std::vector<std::string_view>{
+        "AL_EXT_ALAW"sv,
+        "AL_EXT_BFORMAT"sv,
+        "AL_EXT_debug"sv,
+        "AL_EXT_direct_context"sv,
+        "AL_EXT_DOUBLE"sv,
+        "AL_EXT_EXPONENT_DISTANCE"sv,
+        "AL_EXT_FLOAT32"sv,
+        "AL_EXT_IMA4"sv,
+        "AL_EXT_LINEAR_DISTANCE"sv,
+        "AL_EXT_MCFORMATS"sv,
+        "AL_EXT_MULAW"sv,
+        "AL_EXT_MULAW_BFORMAT"sv,
+        "AL_EXT_MULAW_MCFORMATS"sv,
+        "AL_EXT_OFFSET"sv,
+        "AL_EXT_source_distance_model"sv,
+        "AL_EXT_SOURCE_RADIUS"sv,
+        "AL_EXT_STATIC_BUFFER"sv,
+        "AL_EXT_STEREO_ANGLES"sv,
+        "AL_LOKI_quadriphonic"sv,
+        "AL_SOFT_bformat_ex"sv,
+        "AL_SOFTX_bformat_hoa"sv,
+        "AL_SOFT_block_alignment"sv,
+        "AL_SOFT_buffer_length_query"sv,
+        "AL_SOFT_callback_buffer"sv,
+        "AL_SOFTX_convolution_effect"sv,
+        "AL_SOFT_deferred_updates"sv,
+        "AL_SOFT_direct_channels"sv,
+        "AL_SOFT_direct_channels_remix"sv,
+        "AL_SOFT_effect_target"sv,
+        "AL_SOFT_events"sv,
+        "AL_SOFT_gain_clamp_ex"sv,
+        "AL_SOFTX_hold_on_disconnect"sv,
+        "AL_SOFT_loop_points"sv,
+        "AL_SOFTX_map_buffer"sv,
+        "AL_SOFT_MSADPCM"sv,
+        "AL_SOFT_source_latency"sv,
+        "AL_SOFT_source_length"sv,
+        "AL_SOFTX_source_panning"sv,
+        "AL_SOFT_source_resampler"sv,
+        "AL_SOFT_source_spatialize"sv,
+        "AL_SOFT_source_start_delay"sv,
+        "AL_SOFT_UHJ"sv,
+        "AL_SOFT_UHJ_ex"sv,
+    };
+}
 
 } // namespace
 
 
+std::atomic<bool> ALCcontext::sGlobalContextLock{false};
 std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
 
-thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
 ALCcontext::ThreadCtx::~ThreadCtx()
 {
-    if(ALCcontext *ctx{ALCcontext::sLocalContext})
+    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},
@@ -103,23 +117,18 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
 ALeffect ALCcontext::sDefaultEffect;
 
 
-#ifdef __MINGW32__
-ALCcontext *ALCcontext::getThreadContext() noexcept
-{ return sLocalContext; }
-void ALCcontext::setThreadContext(ALCcontext *context) noexcept
-{ sThreadContext.set(context); }
-#endif
-
-ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device)
-  : ContextBase{device.get()}, mALDevice{std::move(device)}
+ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> 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);
 }
 
 ALCcontext::~ALCcontext()
 {
     TRACE("Freeing context %p\n", voidp{this});
 
-    size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
+    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)
@@ -128,11 +137,11 @@ ALCcontext::~ALCcontext()
     mNumSources = 0;
 
 #ifdef ALSOFT_EAX
-    eax_uninitialize();
+    eaxUninitialize();
 #endif // ALSOFT_EAX
 
     mDefaultSlot = nullptr;
-    count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
+    count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz,
         [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
         { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
     if(count > 0)
@@ -145,20 +154,21 @@ void ALCcontext::init()
 {
     if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback)
     {
-        mDefaultSlot = std::make_unique<ALeffectslot>();
-        aluInitEffectPanning(&mDefaultSlot->mSlot, this);
+        mDefaultSlot = std::make_unique<ALeffectslot>(this);
+        aluInitEffectPanning(mDefaultSlot->mSlot, this);
     }
 
-    EffectSlotArray *auxslots;
+    std::unique_ptr<EffectSlotArray> auxslots;
     if(!mDefaultSlot)
         auxslots = EffectSlot::CreatePtrArray(0);
     else
     {
         auxslots = EffectSlot::CreatePtrArray(1);
-        (*auxslots)[0] = &mDefaultSlot->mSlot;
+        (*auxslots)[0] = mDefaultSlot->mSlot;
+        std::uninitialized_fill_n(al::to_address(auxslots->end()), 1_uz, nullptr);
         mDefaultSlot->mState = SlotState::Playing;
     }
-    mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
+    mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed);
 
     allocVoiceChanges();
     {
@@ -168,12 +178,41 @@ void ALCcontext::init()
         mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
     }
 
-    mExtensionList = alExtList;
+    mExtensions = getContextExtensions();
+
+    if(sBufferSubDataCompat)
+    {
+        auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS");
+        if(iter != mExtensions.end()) mExtensions.erase(iter);
+        /* TODO: Would be nice to sort this alphabetically. Needs case-
+         * insensitive searching.
+         */
+        mExtensions.emplace_back("AL_SOFT_buffer_sub_data");
+    }
 
 #ifdef ALSOFT_EAX
     eax_initialize_extensions();
 #endif // ALSOFT_EAX
 
+    if(!mExtensions.empty())
+    {
+        const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(),
+            mExtensions.front().length(),
+            [](size_t current, std::string_view ext) noexcept
+            { return current + ext.length() + 1; })};
+
+        std::string extensions;
+        extensions.reserve(len);
+        extensions += mExtensions.front();
+        for(std::string_view ext : al::span{mExtensions}.subspan<1>())
+        {
+            extensions += ' ';
+            extensions += ext;
+        }
+
+        mExtensionsString = std::move(extensions);
+    }
+
     mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
     mParams.Matrix = alu::Matrix::Identity();
     mParams.Velocity = alu::Vector{};
@@ -194,56 +233,59 @@ void ALCcontext::init()
     mActiveVoiceCount.store(64, std::memory_order_relaxed);
 }
 
-bool ALCcontext::deinit()
+void ALCcontext::deinit()
 {
     if(sLocalContext == this)
     {
         WARN("%p released while current on thread\n", voidp{this});
         sThreadContext.set(nullptr);
-        release();
+        dec_ref();
     }
 
     ALCcontext *origctx{this};
     if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
-        release();
+    {
+        while(sGlobalContextLock.load()) {
+            /* Wait to make sure another thread didn't get the context and is
+             * trying to increment its refcount.
+             */
+        }
+        dec_ref();
+    }
 
-    bool ret{};
+    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)))
     {
         using ContextArray = al::FlexArray<ContextBase*>;
-        auto alloc_ctx_array = [](const size_t count) -> ContextArray*
-        {
-            if(count == 0) return &DeviceBase::sEmptyContextArray;
-            return ContextArray::Create(count).release();
-        };
-        auto *newarray = alloc_ctx_array(oldarray->size() - toremove);
+        const size_t newsize{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::bind(std::not_equal_to<>{}, _1, this));
+            [this](ContextBase *ctx) { return ctx != this; });
 
         /* Store the new context array in the device. Wait for any current mix
          * to finish before deleting the old array.
          */
-        mDevice->mContexts.store(newarray);
-        if(oldarray != &DeviceBase::sEmptyContextArray)
-        {
-            mDevice->waitForMix();
-            delete oldarray;
-        }
+        auto prevarray = mDevice->mContexts.exchange(std::move(newarray));
+        std::ignore = mDevice->waitForMix();
 
-        ret = !newarray->empty();
+        stopPlayback = (newsize == 0);
     }
     else
-        ret = !oldarray->empty();
+        stopPlayback = oldarray->empty();
 
     StopEventThrd(this);
 
-    return ret;
+    if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing)
+    {
+        mALDevice->Backend->stop();
+        mALDevice->mDeviceState = DeviceState::Configured;
+    }
 }
 
 void ALCcontext::applyAllUpdates()
@@ -257,8 +299,10 @@ void ALCcontext::applyAllUpdates()
     }
 
 #ifdef ALSOFT_EAX
-    eax_apply_deferred();
+    if(mEaxNeedsCommit)
+        eaxCommit();
 #endif
+
     if(std::exchange(mPropsDirty, false))
         UpdateContextProps(this);
     UpdateAllEffectSlotProps(this);
@@ -270,22 +314,10 @@ void ALCcontext::applyAllUpdates()
     mHoldUpdates.store(false, std::memory_order_release);
 }
 
+
 #ifdef ALSOFT_EAX
 namespace {
 
-class ContextException :
-    public EaxException
-{
-public:
-    explicit ContextException(
-        const char* message)
-        :
-        EaxException{"EAX_CONTEXT", message}
-    {
-    }
-}; // ContextException
-
-
 template<typename F>
 void ForEachSource(ALCcontext *context, F func)
 {
@@ -294,10 +326,10 @@ void ForEachSource(ALCcontext *context, F func)
         uint64_t usemask{~sublist.FreeMask};
         while(usemask)
         {
-            const int idx{al::countr_zero(usemask)};
+            const auto idx = static_cast<uint>(al::countr_zero(usemask));
             usemask &= ~(1_u64 << idx);
 
-            func(sublist.Sources[idx]);
+            func((*sublist.Sources)[idx]);
         }
     }
 }
@@ -305,22 +337,19 @@ void ForEachSource(ALCcontext *context, F func)
 } // namespace
 
 
-bool ALCcontext::eax_is_capable() const noexcept
+bool ALCcontext::eaxIsCapable() const noexcept
 {
     return eax_has_enough_aux_sends();
 }
 
-void ALCcontext::eax_uninitialize() noexcept
+void ALCcontext::eaxUninitialize() noexcept
 {
-    if (!eax_is_initialized_)
-    {
+    if(!mEaxIsInitialized)
         return;
-    }
-
-    eax_is_initialized_ = true;
-    eax_is_tried_ = false;
 
-    eax_fx_slots_.uninitialize();
+    mEaxIsInitialized = false;
+    mEaxIsTried = false;
+    mEaxFxSlots.uninitialize();
 }
 
 ALenum ALCcontext::eax_eax_set(
@@ -330,41 +359,39 @@ ALenum ALCcontext::eax_eax_set(
     ALvoid* property_value,
     ALuint property_value_size)
 {
-    eax_initialize();
-
-    const auto eax_call = create_eax_call(
-        false,
+    const auto call = create_eax_call(
+        EaxCallType::set,
         property_set_id,
         property_id,
         property_source_id,
         property_value,
-        property_value_size
-    );
+        property_value_size);
 
-    eax_unlock_legacy_fx_slots(eax_call);
+    eax_initialize();
 
-    switch (eax_call.get_property_set_id())
+    switch(call.get_property_set_id())
     {
-        case EaxEaxCallPropertySetId::context:
-            eax_set(eax_call);
-            break;
-
-        case EaxEaxCallPropertySetId::fx_slot:
-        case EaxEaxCallPropertySetId::fx_slot_effect:
-            eax_dispatch_fx_slot(eax_call);
-            break;
-
-        case EaxEaxCallPropertySetId::source:
-            eax_dispatch_source(eax_call);
-            break;
-
-        default:
-            eax_fail("Unsupported property set id.");
+    case EaxCallPropertySetId::context:
+        eax_set(call);
+        break;
+    case EaxCallPropertySetId::fx_slot:
+    case EaxCallPropertySetId::fx_slot_effect:
+        eax_dispatch_fx_slot(call);
+        break;
+    case EaxCallPropertySetId::source:
+        eax_dispatch_source(call);
+        break;
+    default:
+        eax_fail_unknown_property_set_id();
     }
+    mEaxNeedsCommit = true;
 
-    static constexpr auto deferred_flag = 0x80000000u;
-    if(!(property_id&deferred_flag) && !mDeferUpdates)
-        applyAllUpdates();
+    if(!call.is_deferred())
+    {
+        eaxCommit();
+        if(!mDeferUpdates)
+            applyAllUpdates();
+    }
 
     return AL_NO_ERROR;
 }
@@ -376,135 +403,101 @@ ALenum ALCcontext::eax_eax_get(
     ALvoid* property_value,
     ALuint property_value_size)
 {
-    eax_initialize();
-
-    const auto eax_call = create_eax_call(
-        true,
+    const auto call = create_eax_call(
+        EaxCallType::get,
         property_set_id,
         property_id,
         property_source_id,
         property_value,
-        property_value_size
-    );
+        property_value_size);
 
-    eax_unlock_legacy_fx_slots(eax_call);
+    eax_initialize();
 
-    switch (eax_call.get_property_set_id())
+    switch(call.get_property_set_id())
     {
-        case EaxEaxCallPropertySetId::context:
-            eax_get(eax_call);
-            break;
-
-        case EaxEaxCallPropertySetId::fx_slot:
-        case EaxEaxCallPropertySetId::fx_slot_effect:
-            eax_dispatch_fx_slot(eax_call);
-            break;
-
-        case EaxEaxCallPropertySetId::source:
-            eax_dispatch_source(eax_call);
-            break;
-
-        default:
-            eax_fail("Unsupported property set id.");
+    case EaxCallPropertySetId::context:
+        eax_get(call);
+        break;
+    case EaxCallPropertySetId::fx_slot:
+    case EaxCallPropertySetId::fx_slot_effect:
+        eax_dispatch_fx_slot(call);
+        break;
+    case EaxCallPropertySetId::source:
+        eax_dispatch_source(call);
+        break;
+    default:
+        eax_fail_unknown_property_set_id();
     }
 
     return AL_NO_ERROR;
 }
 
-void ALCcontext::eax_update_filters()
+void ALCcontext::eaxSetLastError() noexcept
 {
-    ForEachSource(this, std::mem_fn(&ALsource::eax_update_filters));
+    mEaxLastError = EAXERR_INVALID_OPERATION;
 }
 
-void ALCcontext::eax_commit_and_update_sources()
+[[noreturn]] void ALCcontext::eax_fail(const char* message)
 {
-    std::unique_lock<std::mutex> source_lock{mSourceLock};
-    ForEachSource(this, std::mem_fn(&ALsource::eax_commit_and_update));
+    throw ContextException{message};
 }
 
-void ALCcontext::eax_set_last_error() noexcept
+[[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id()
 {
-    eax_last_error_ = EAXERR_INVALID_OPERATION;
+    eax_fail("Unknown property ID.");
 }
 
-[[noreturn]]
-void ALCcontext::eax_fail(
-    const char* message)
+[[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id()
 {
-    throw ContextException{message};
+    eax_fail("Unknown primary FX Slot ID.");
 }
 
-void ALCcontext::eax_initialize_extensions()
+[[noreturn]] void ALCcontext::eax_fail_unknown_property_id()
 {
-    if (!eax_g_is_enabled)
-    {
-        return;
-    }
+    eax_fail("Unknown property ID.");
+}
 
-    const auto string_max_capacity =
-        std::strlen(mExtensionList) + 1 +
-        std::strlen(eax1_ext_name) + 1 +
-        std::strlen(eax2_ext_name) + 1 +
-        std::strlen(eax3_ext_name) + 1 +
-        std::strlen(eax4_ext_name) + 1 +
-        std::strlen(eax5_ext_name) + 1 +
-        std::strlen(eax_x_ram_ext_name) + 1 +
-        0;
+[[noreturn]] void ALCcontext::eax_fail_unknown_version()
+{
+    eax_fail("Unknown version.");
+}
 
-    eax_extension_list_.reserve(string_max_capacity);
+void ALCcontext::eax_initialize_extensions()
+{
+    if(!eax_g_is_enabled)
+        return;
 
-    if (eax_is_capable())
+    mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv);
+    if(eaxIsCapable())
     {
-        eax_extension_list_ += eax1_ext_name;
-        eax_extension_list_ += ' ';
-
-        eax_extension_list_ += eax2_ext_name;
-        eax_extension_list_ += ' ';
-
-        eax_extension_list_ += eax3_ext_name;
-        eax_extension_list_ += ' ';
-
-        eax_extension_list_ += eax4_ext_name;
-        eax_extension_list_ += ' ';
-
-        eax_extension_list_ += eax5_ext_name;
-        eax_extension_list_ += ' ';
+        mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv);
+        mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv);
+        mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv);
+        mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv);
+        mExtensions.emplace(mExtensions.begin(), "EAX"sv);
     }
-
-    eax_extension_list_ += eax_x_ram_ext_name;
-    eax_extension_list_ += ' ';
-
-    eax_extension_list_ += mExtensionList;
-    mExtensionList = eax_extension_list_.c_str();
 }
 
 void ALCcontext::eax_initialize()
 {
-    if (eax_is_initialized_)
-    {
+    if(mEaxIsInitialized)
         return;
-    }
 
-    if (eax_is_tried_)
-    {
+    if(mEaxIsTried)
         eax_fail("No EAX.");
-    }
 
-    eax_is_tried_ = true;
+    mEaxIsTried = true;
 
-    if (!eax_g_is_enabled)
-    {
+    if(!eax_g_is_enabled)
         eax_fail("EAX disabled by a configuration.");
-    }
 
     eax_ensure_compatibility();
     eax_set_defaults();
-    eax_set_air_absorbtion_hf();
+    eax_context_commit_air_absorbtion_hf();
     eax_update_speaker_configuration();
     eax_initialize_fx_slots();
-    eax_initialize_sources();
 
-    eax_is_initialized_ = true;
+    mEaxIsInitialized = true;
 }
 
 bool ALCcontext::eax_has_no_default_effect_slot() const noexcept
@@ -514,10 +507,8 @@ bool ALCcontext::eax_has_no_default_effect_slot() const noexcept
 
 void ALCcontext::eax_ensure_no_default_effect_slot() const
 {
-    if (!eax_has_no_default_effect_slot())
-    {
+    if(!eax_has_no_default_effect_slot())
         eax_fail("There is a default effect slot in the context.");
-    }
 }
 
 bool ALCcontext::eax_has_enough_aux_sends() const noexcept
@@ -527,10 +518,8 @@ bool ALCcontext::eax_has_enough_aux_sends() const noexcept
 
 void ALCcontext::eax_ensure_enough_aux_sends() const
 {
-    if (!eax_has_enough_aux_sends())
-    {
+    if(!eax_has_enough_aux_sends())
         eax_fail("Not enough aux sends.");
-    }
 }
 
 void ALCcontext::eax_ensure_compatibility()
@@ -558,6 +547,14 @@ 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
+     * suggest with-height surround sound (like HRTF).
+     */
+    case DevFmtX714: return SPEAKERS_7;
+    /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to
+     * suggest full-sphere surround sound (like HRTF).
+     */
+    case DevFmtX3D71: return SPEAKERS_5;
     /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D
      * provide full-sphere surround sound. Depends if apps are more likely to
      * consider headphones or 7.1 for surround sound support.
@@ -572,728 +569,491 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const
 
 void ALCcontext::eax_update_speaker_configuration()
 {
-    eax_speaker_config_ = eax_detect_speaker_configuration();
+    mEaxSpeakerConfig = eax_detect_speaker_configuration();
 }
 
 void ALCcontext::eax_set_last_error_defaults() noexcept
 {
-    eax_last_error_ = EAX_OK;
+    mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR;
 }
 
-void ALCcontext::eax_set_session_defaults() noexcept
+void ALCcontext::eax_session_set_defaults() noexcept
 {
-    eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION;
-    eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
+    mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION;
+    mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
 }
 
-void ALCcontext::eax_set_context_defaults() noexcept
+void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept
 {
-    eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID;
-    eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
-    eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
-    eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+    props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID;
+    props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+    props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+    props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
 }
 
-void ALCcontext::eax_set_defaults() noexcept
+void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept
 {
-    eax_set_last_error_defaults();
-    eax_set_session_defaults();
-    eax_set_context_defaults();
-
-    eax_d_ = eax_;
+    eax4_context_set_defaults(state.i);
+    state.d = state.i;
 }
 
-void ALCcontext::eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept
+void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept
 {
-    if (eax_call.get_version() != 5 || eax_are_legacy_fx_slots_unlocked_)
-        return;
-
-    eax_are_legacy_fx_slots_unlocked_ = true;
-    eax_fx_slots_.unlock_legacy();
+    props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID;
+    props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+    props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+    props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+    props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR;
 }
 
-void ALCcontext::eax_dispatch_fx_slot(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept
 {
-    const auto fx_slot_index = eax_call.get_fx_slot_index();
-    if(!fx_slot_index.has_value())
-        eax_fail("Invalid fx slot index.");
-
-    auto& fx_slot = eax_get_fx_slot(*fx_slot_index);
-    if(fx_slot.eax_dispatch(eax_call))
-    {
-        std::lock_guard<std::mutex> source_lock{mSourceLock};
-        eax_update_filters();
-    }
+    eax5_context_set_defaults(state.i);
+    state.d = state.i;
 }
 
-void ALCcontext::eax_dispatch_source(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_context_set_defaults()
 {
-    const auto source_id = eax_call.get_property_al_name();
-
-    std::lock_guard<std::mutex> source_lock{mSourceLock};
-
-    const auto source = ALsource::eax_lookup_source(*this, source_id);
-
-    if (!source)
-    {
-        eax_fail("Source not found.");
-    }
-
-    source->eax_dispatch(eax_call);
+    eax5_context_set_defaults(mEax123);
+    eax4_context_set_defaults(mEax4);
+    eax5_context_set_defaults(mEax5);
+    mEax = mEax5.i;
+    mEaxVersion = 5;
+    mEaxDf = EaxDirtyFlags{};
 }
 
-void ALCcontext::eax_get_primary_fx_slot_id(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_set_defaults()
 {
-    eax_call.set_value<ContextException>(eax_.context.guidPrimaryFXSlotID);
+    eax_set_last_error_defaults();
+    eax_session_set_defaults();
+    eax_context_set_defaults();
 }
 
-void ALCcontext::eax_get_distance_factor(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call)
 {
-    eax_call.set_value<ContextException>(eax_.context.flDistanceFactor);
-}
+    const auto fx_slot_index = call.get_fx_slot_index();
+    if(!fx_slot_index.has_value())
+        eax_fail("Invalid fx slot index.");
 
-void ALCcontext::eax_get_air_absorption_hf(
-    const EaxEaxCall& eax_call)
-{
-    eax_call.set_value<ContextException>(eax_.context.flAirAbsorptionHF);
+    auto& fx_slot = eaxGetFxSlot(*fx_slot_index);
+    if(fx_slot.eax_dispatch(call))
+    {
+        std::lock_guard<std::mutex> source_lock{mSourceLock};
+        ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged));
+    }
 }
 
-void ALCcontext::eax_get_hf_reference(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_dispatch_source(const EaxCall& call)
 {
-    eax_call.set_value<ContextException>(eax_.context.flHFReference);
-}
+    const auto source_id = call.get_property_al_name();
+    std::lock_guard<std::mutex> source_lock{mSourceLock};
+    const auto source = ALsource::EaxLookupSource(*this, source_id);
 
-void ALCcontext::eax_get_last_error(
-    const EaxEaxCall& eax_call)
-{
-    const auto eax_last_error = eax_last_error_;
-    eax_last_error_ = EAX_OK;
-    eax_call.set_value<ContextException>(eax_last_error);
-}
+    if (source == nullptr)
+        eax_fail("Source not found.");
 
-void ALCcontext::eax_get_speaker_config(
-    const EaxEaxCall& eax_call)
-{
-    eax_call.set_value<ContextException>(eax_speaker_config_);
+    source->eaxDispatch(call);
 }
 
-void ALCcontext::eax_get_session(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_get_misc(const EaxCall& call)
 {
-    eax_call.set_value<ContextException>(eax_session_);
+    switch(call.get_property_id())
+    {
+    case EAXCONTEXT_NONE:
+        break;
+    case EAXCONTEXT_LASTERROR:
+        call.set_value<ContextException>(mEaxLastError);
+        mEaxLastError = EAX_OK;
+        break;
+    case EAXCONTEXT_SPEAKERCONFIG:
+        call.set_value<ContextException>(mEaxSpeakerConfig);
+        break;
+    case EAXCONTEXT_EAXSESSION:
+        call.set_value<ContextException>(mEaxSession);
+        break;
+    default:
+        eax_fail_unknown_property_id();
+    }
 }
 
-void ALCcontext::eax_get_macro_fx_factor(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props)
 {
-    eax_call.set_value<ContextException>(eax_.context.flMacroFXFactor);
+    switch(call.get_property_id())
+    {
+    case EAXCONTEXT_ALLPARAMETERS:
+        call.set_value<ContextException>(props);
+        break;
+    case EAXCONTEXT_PRIMARYFXSLOTID:
+        call.set_value<ContextException>(props.guidPrimaryFXSlotID);
+        break;
+    case EAXCONTEXT_DISTANCEFACTOR:
+        call.set_value<ContextException>(props.flDistanceFactor);
+        break;
+    case EAXCONTEXT_AIRABSORPTIONHF:
+        call.set_value<ContextException>(props.flAirAbsorptionHF);
+        break;
+    case EAXCONTEXT_HFREFERENCE:
+        call.set_value<ContextException>(props.flHFReference);
+        break;
+    default:
+        eax_get_misc(call);
+        break;
+    }
 }
 
-void ALCcontext::eax_get_context_all(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props)
 {
-    switch (eax_call.get_version())
+    switch(call.get_property_id())
     {
-        case 4:
-            eax_call.set_value<ContextException>(static_cast<const EAX40CONTEXTPROPERTIES&>(eax_.context));
-            break;
-
-        case 5:
-            eax_call.set_value<ContextException>(static_cast<const EAX50CONTEXTPROPERTIES&>(eax_.context));
-            break;
-
-        default:
-            eax_fail("Unsupported EAX version.");
+    case EAXCONTEXT_ALLPARAMETERS:
+        call.set_value<ContextException>(props);
+        break;
+    case EAXCONTEXT_PRIMARYFXSLOTID:
+        call.set_value<ContextException>(props.guidPrimaryFXSlotID);
+        break;
+    case EAXCONTEXT_DISTANCEFACTOR:
+        call.set_value<ContextException>(props.flDistanceFactor);
+        break;
+    case EAXCONTEXT_AIRABSORPTIONHF:
+        call.set_value<ContextException>(props.flAirAbsorptionHF);
+        break;
+    case EAXCONTEXT_HFREFERENCE:
+        call.set_value<ContextException>(props.flHFReference);
+        break;
+    case EAXCONTEXT_MACROFXFACTOR:
+        call.set_value<ContextException>(props.flMacroFXFactor);
+        break;
+    default:
+        eax_get_misc(call);
+        break;
     }
 }
 
-void ALCcontext::eax_get(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_get(const EaxCall& call)
 {
-    switch (eax_call.get_property_id())
+    switch(call.get_version())
     {
-        case EAXCONTEXT_NONE:
-            break;
-
-        case EAXCONTEXT_ALLPARAMETERS:
-            eax_get_context_all(eax_call);
-            break;
-
-        case EAXCONTEXT_PRIMARYFXSLOTID:
-            eax_get_primary_fx_slot_id(eax_call);
-            break;
-
-        case EAXCONTEXT_DISTANCEFACTOR:
-            eax_get_distance_factor(eax_call);
-            break;
-
-        case EAXCONTEXT_AIRABSORPTIONHF:
-            eax_get_air_absorption_hf(eax_call);
-            break;
-
-        case EAXCONTEXT_HFREFERENCE:
-            eax_get_hf_reference(eax_call);
-            break;
-
-        case EAXCONTEXT_LASTERROR:
-            eax_get_last_error(eax_call);
-            break;
-
-        case EAXCONTEXT_SPEAKERCONFIG:
-            eax_get_speaker_config(eax_call);
-            break;
-
-        case EAXCONTEXT_EAXSESSION:
-            eax_get_session(eax_call);
-            break;
-
-        case EAXCONTEXT_MACROFXFACTOR:
-            eax_get_macro_fx_factor(eax_call);
-            break;
-
-        default:
-            eax_fail("Unsupported property id.");
+    case 4: eax4_get(call, mEax4.i); break;
+    case 5: eax5_get(call, mEax5.i); break;
+    default: eax_fail_unknown_version();
     }
 }
 
-void ALCcontext::eax_set_primary_fx_slot_id()
+void ALCcontext::eax_context_commit_primary_fx_slot_id()
 {
-    eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_;
-    eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
+    mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
 }
 
-void ALCcontext::eax_set_distance_factor()
+void ALCcontext::eax_context_commit_distance_factor()
 {
-    mListener.mMetersPerUnit = eax_.context.flDistanceFactor;
+    if(mListener.mMetersPerUnit == mEax.flDistanceFactor)
+        return;
+
+    mListener.mMetersPerUnit = mEax.flDistanceFactor;
     mPropsDirty = true;
 }
 
-void ALCcontext::eax_set_air_absorbtion_hf()
+void ALCcontext::eax_context_commit_air_absorbtion_hf()
 {
-    mAirAbsorptionGainHF = level_mb_to_gain(eax_.context.flAirAbsorptionHF);
+    const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF);
+
+    if(mAirAbsorptionGainHF == new_value)
+        return;
+
+    mAirAbsorptionGainHF = new_value;
     mPropsDirty = true;
 }
 
-void ALCcontext::eax_set_hf_reference()
+void ALCcontext::eax_context_commit_hf_reference()
 {
     // TODO
 }
 
-void ALCcontext::eax_set_macro_fx_factor()
+void ALCcontext::eax_context_commit_macro_fx_factor()
 {
     // TODO
 }
 
-void ALCcontext::eax_set_context()
-{
-    eax_set_primary_fx_slot_id();
-    eax_set_distance_factor();
-    eax_set_air_absorbtion_hf();
-    eax_set_hf_reference();
-}
-
 void ALCcontext::eax_initialize_fx_slots()
 {
-    eax_fx_slots_.initialize(*this);
-    eax_previous_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
-    eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
-}
-
-void ALCcontext::eax_initialize_sources()
-{
-    std::unique_lock<std::mutex> source_lock{mSourceLock};
-    auto init_source = [this](ALsource &source) noexcept
-    { source.eax_initialize(this); };
-    ForEachSource(this, init_source);
+    mEaxFxSlots.initialize(*this);
+    mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID;
 }
 
 void ALCcontext::eax_update_sources()
 {
     std::unique_lock<std::mutex> source_lock{mSourceLock};
-    auto update_source = [this](ALsource &source)
-    { source.eax_update(eax_context_shared_dirty_flags_); };
+    auto update_source = [](ALsource &source)
+    { source.eaxCommit(); };
     ForEachSource(this, update_source);
 }
 
-void ALCcontext::eax_validate_primary_fx_slot_id(
-    const GUID& primary_fx_slot_id)
+void ALCcontext::eax_set_misc(const EaxCall& call)
 {
-    if (primary_fx_slot_id != EAX_NULL_GUID &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot0 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot0 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot1 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot1 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot2 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot2 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot3 &&
-        primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot3)
+    switch(call.get_property_id())
     {
-        eax_fail("Unsupported primary FX slot id.");
+    case EAXCONTEXT_NONE:
+        break;
+    case EAXCONTEXT_SPEAKERCONFIG:
+        eax_set<Eax5SpeakerConfigValidator>(call, mEaxSpeakerConfig);
+        break;
+    case EAXCONTEXT_EAXSESSION:
+        eax_set<Eax5SessionAllValidator>(call, mEaxSession);
+        break;
+    default:
+        eax_fail_unknown_property_id();
     }
 }
 
-void ALCcontext::eax_validate_distance_factor(
-    float distance_factor)
+void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state)
 {
-    eax_validate_range<ContextException>(
-        "Distance Factor",
-        distance_factor,
-        EAXCONTEXT_MINDISTANCEFACTOR,
-        EAXCONTEXT_MAXDISTANCEFACTOR);
-}
+    const auto& src = call.get_value<ContextException, const EAX40CONTEXTPROPERTIES>();
+    Eax4AllValidator{}(src);
+    const auto& dst_i = state.i;
+    auto& dst_d = state.d;
+    dst_d = src;
 
-void ALCcontext::eax_validate_air_absorption_hf(
-    float air_absorption_hf)
-{
-    eax_validate_range<ContextException>(
-        "Air Absorption HF",
-        air_absorption_hf,
-        EAXCONTEXT_MINAIRABSORPTIONHF,
-        EAXCONTEXT_MAXAIRABSORPTIONHF);
-}
+    if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
+        mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
 
-void ALCcontext::eax_validate_hf_reference(
-    float hf_reference)
-{
-    eax_validate_range<ContextException>(
-        "HF Reference",
-        hf_reference,
-        EAXCONTEXT_MINHFREFERENCE,
-        EAXCONTEXT_MAXHFREFERENCE);
-}
+    if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
+        mEaxDf |= eax_distance_factor_dirty_bit;
 
-void ALCcontext::eax_validate_speaker_config(
-    unsigned long speaker_config)
-{
-    switch (speaker_config)
-    {
-        case HEADPHONES:
-        case SPEAKERS_2:
-        case SPEAKERS_4:
-        case SPEAKERS_5:
-        case SPEAKERS_6:
-        case SPEAKERS_7:
-            break;
-
-        default:
-            eax_fail("Unsupported speaker configuration.");
-    }
+    if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
+        mEaxDf |= eax_air_absorption_hf_dirty_bit;
+
+    if(dst_i.flHFReference != dst_d.flHFReference)
+        mEaxDf |= eax_hf_reference_dirty_bit;
 }
 
-void ALCcontext::eax_validate_session_eax_version(
-    unsigned long eax_version)
+void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state)
 {
-    switch (eax_version)
+    switch(call.get_property_id())
     {
-        case EAX_40:
-        case EAX_50:
-            break;
-
-        default:
-            eax_fail("Unsupported session EAX version.");
+    case EAXCONTEXT_ALLPARAMETERS:
+        eax4_defer_all(call, state);
+        break;
+    case EAXCONTEXT_PRIMARYFXSLOTID:
+        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);
+        break;
+    case EAXCONTEXT_AIRABSORPTIONHF:
+        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);
+        break;
+    default:
+        eax_set_misc(call);
+        break;
     }
 }
 
-void ALCcontext::eax_validate_session_max_active_sends(
-    unsigned long max_active_sends)
-{
-    eax_validate_range<ContextException>(
-        "Max Active Sends",
-        max_active_sends,
-        EAXCONTEXT_MINMAXACTIVESENDS,
-        EAXCONTEXT_MAXMAXACTIVESENDS);
-}
-
-void ALCcontext::eax_validate_session(
-    const EAXSESSIONPROPERTIES& eax_session)
-{
-    eax_validate_session_eax_version(eax_session.ulEAXVersion);
-    eax_validate_session_max_active_sends(eax_session.ulMaxActiveSends);
-}
-
-void ALCcontext::eax_validate_macro_fx_factor(
-    float macro_fx_factor)
-{
-    eax_validate_range<ContextException>(
-        "Macro FX Factor",
-        macro_fx_factor,
-        EAXCONTEXT_MINMACROFXFACTOR,
-        EAXCONTEXT_MAXMACROFXFACTOR);
-}
-
-void ALCcontext::eax_validate_context_all(
-    const EAX40CONTEXTPROPERTIES& context_all)
-{
-    eax_validate_primary_fx_slot_id(context_all.guidPrimaryFXSlotID);
-    eax_validate_distance_factor(context_all.flDistanceFactor);
-    eax_validate_air_absorption_hf(context_all.flAirAbsorptionHF);
-    eax_validate_hf_reference(context_all.flHFReference);
-}
-
-void ALCcontext::eax_validate_context_all(
-    const EAX50CONTEXTPROPERTIES& context_all)
-{
-    eax_validate_context_all(static_cast<const EAX40CONTEXTPROPERTIES>(context_all));
-    eax_validate_macro_fx_factor(context_all.flMacroFXFactor);
-}
-
-void ALCcontext::eax_defer_primary_fx_slot_id(
-    const GUID& primary_fx_slot_id)
-{
-    eax_d_.context.guidPrimaryFXSlotID = primary_fx_slot_id;
-
-    eax_context_dirty_flags_.guidPrimaryFXSlotID =
-        (eax_.context.guidPrimaryFXSlotID != eax_d_.context.guidPrimaryFXSlotID);
-}
-
-void ALCcontext::eax_defer_distance_factor(
-    float distance_factor)
-{
-    eax_d_.context.flDistanceFactor = distance_factor;
-
-    eax_context_dirty_flags_.flDistanceFactor =
-        (eax_.context.flDistanceFactor != eax_d_.context.flDistanceFactor);
-}
-
-void ALCcontext::eax_defer_air_absorption_hf(
-    float air_absorption_hf)
+void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state)
 {
-    eax_d_.context.flAirAbsorptionHF = air_absorption_hf;
+    const auto& src = call.get_value<ContextException, const EAX50CONTEXTPROPERTIES>();
+    Eax4AllValidator{}(src);
+    const auto& dst_i = state.i;
+    auto& dst_d = state.d;
+    dst_d = src;
 
-    eax_context_dirty_flags_.flAirAbsorptionHF =
-        (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF);
-}
+    if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID)
+        mEaxDf |= eax_primary_fx_slot_id_dirty_bit;
 
-void ALCcontext::eax_defer_hf_reference(
-    float hf_reference)
-{
-    eax_d_.context.flHFReference = hf_reference;
+    if(dst_i.flDistanceFactor != dst_d.flDistanceFactor)
+        mEaxDf |= eax_distance_factor_dirty_bit;
 
-    eax_context_dirty_flags_.flHFReference =
-        (eax_.context.flHFReference != eax_d_.context.flHFReference);
-}
+    if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF)
+        mEaxDf |= eax_air_absorption_hf_dirty_bit;
 
-void ALCcontext::eax_defer_macro_fx_factor(
-    float macro_fx_factor)
-{
-    eax_d_.context.flMacroFXFactor = macro_fx_factor;
+    if(dst_i.flHFReference != dst_d.flHFReference)
+        mEaxDf |= eax_hf_reference_dirty_bit;
 
-    eax_context_dirty_flags_.flMacroFXFactor =
-        (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor);
+    if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor)
+        mEaxDf |= eax_macro_fx_factor_dirty_bit;
 }
 
-void ALCcontext::eax_defer_context_all(
-    const EAX40CONTEXTPROPERTIES& context_all)
+void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state)
 {
-    eax_defer_primary_fx_slot_id(context_all.guidPrimaryFXSlotID);
-    eax_defer_distance_factor(context_all.flDistanceFactor);
-    eax_defer_air_absorption_hf(context_all.flAirAbsorptionHF);
-    eax_defer_hf_reference(context_all.flHFReference);
-}
-
-void ALCcontext::eax_defer_context_all(
-    const EAX50CONTEXTPROPERTIES& context_all)
-{
-    eax_defer_context_all(static_cast<const EAX40CONTEXTPROPERTIES&>(context_all));
-    eax_defer_macro_fx_factor(context_all.flMacroFXFactor);
-}
-
-void ALCcontext::eax_defer_context_all(
-    const EaxEaxCall& eax_call)
-{
-    switch(eax_call.get_version())
+    switch(call.get_property_id())
     {
-    case 4:
-        {
-            const auto& context_all =
-                eax_call.get_value<ContextException, EAX40CONTEXTPROPERTIES>();
-
-            eax_validate_context_all(context_all);
-            eax_defer_context_all(context_all);
-        }
+    case EAXCONTEXT_ALLPARAMETERS:
+        eax5_defer_all(call, state);
         break;
-
-    case 5:
-        {
-            const auto& context_all =
-                eax_call.get_value<ContextException, EAX50CONTEXTPROPERTIES>();
-
-            eax_validate_context_all(context_all);
-            eax_defer_context_all(context_all);
-        }
+    case EAXCONTEXT_PRIMARYFXSLOTID:
+        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);
+        break;
+    case EAXCONTEXT_AIRABSORPTIONHF:
+        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);
+        break;
+    case EAXCONTEXT_MACROFXFACTOR:
+        eax_defer<Eax5MacroFxFactorValidator, eax_macro_fx_factor_dirty_bit>(
+            call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor);
         break;
-
     default:
-        eax_fail("Unsupported EAX version.");
+        eax_set_misc(call);
+        break;
     }
 }
 
-void ALCcontext::eax_defer_primary_fx_slot_id(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_set(const EaxCall& call)
 {
-    const auto& primary_fx_slot_id =
-        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID)>();
-
-    eax_validate_primary_fx_slot_id(primary_fx_slot_id);
-    eax_defer_primary_fx_slot_id(primary_fx_slot_id);
-}
-
-void ALCcontext::eax_defer_distance_factor(
-    const EaxEaxCall& eax_call)
-{
-    const auto& distance_factor =
-        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flDistanceFactor)>();
-
-    eax_validate_distance_factor(distance_factor);
-    eax_defer_distance_factor(distance_factor);
+    const auto version = call.get_version();
+    switch(version)
+    {
+    case 4: eax4_defer(call, mEax4); break;
+    case 5: eax5_defer(call, mEax5); break;
+    default: eax_fail_unknown_version();
+    }
+    if(version != mEaxVersion)
+        mEaxDf = ~EaxDirtyFlags();
+    mEaxVersion = version;
 }
 
-void ALCcontext::eax_defer_air_absorption_hf(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df)
 {
-    const auto& air_absorption_hf =
-        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flAirAbsorptionHF)>();
-
-    eax_validate_air_absorption_hf(air_absorption_hf);
-    eax_defer_air_absorption_hf(air_absorption_hf);
-}
+    if(mEaxDf == EaxDirtyFlags{})
+        return;
 
-void ALCcontext::eax_defer_hf_reference(
-    const EaxEaxCall& eax_call)
-{
-    const auto& hf_reference =
-        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::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);
 
-    eax_validate_hf_reference(hf_reference);
-    eax_defer_hf_reference(hf_reference);
+    mEaxDf = EaxDirtyFlags{};
 }
 
-void ALCcontext::eax_set_session(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df)
 {
-    const auto& eax_session =
-        eax_call.get_value<ContextException, const EAXSESSIONPROPERTIES>();
+    if(mEaxDf == EaxDirtyFlags{})
+        return;
 
-    eax_validate_session(eax_session);
+    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_session_ = eax_session;
+    mEaxDf = EaxDirtyFlags{};
 }
 
-void ALCcontext::eax_defer_macro_fx_factor(
-    const EaxEaxCall& eax_call)
+void ALCcontext::eax_context_commit()
 {
-    const auto& macro_fx_factor =
-        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flMacroFXFactor)>();
+    auto dst_df = EaxDirtyFlags{};
 
-    eax_validate_macro_fx_factor(macro_fx_factor);
-    eax_defer_macro_fx_factor(macro_fx_factor);
-}
-
-void ALCcontext::eax_set(
-    const EaxEaxCall& eax_call)
-{
-    switch (eax_call.get_property_id())
+    switch(mEaxVersion)
     {
-        case EAXCONTEXT_NONE:
-            break;
-
-        case EAXCONTEXT_ALLPARAMETERS:
-            eax_defer_context_all(eax_call);
-            break;
-
-        case EAXCONTEXT_PRIMARYFXSLOTID:
-            eax_defer_primary_fx_slot_id(eax_call);
-            break;
-
-        case EAXCONTEXT_DISTANCEFACTOR:
-            eax_defer_distance_factor(eax_call);
-            break;
-
-        case EAXCONTEXT_AIRABSORPTIONHF:
-            eax_defer_air_absorption_hf(eax_call);
-            break;
-
-        case EAXCONTEXT_HFREFERENCE:
-            eax_defer_hf_reference(eax_call);
-            break;
-
-        case EAXCONTEXT_LASTERROR:
-            eax_fail("Last error is read-only.");
-
-        case EAXCONTEXT_SPEAKERCONFIG:
-            eax_fail("Speaker configuration is read-only.");
-
-        case EAXCONTEXT_EAXSESSION:
-            eax_set_session(eax_call);
-            break;
-
-        case EAXCONTEXT_MACROFXFACTOR:
-            eax_defer_macro_fx_factor(eax_call);
-            break;
-
-        default:
-            eax_fail("Unsupported property id.");
+    case 1:
+    case 2:
+    case 3:
+        eax5_context_commit(mEax123, dst_df);
+        break;
+    case 4:
+        eax4_context_commit(mEax4, dst_df);
+        break;
+    case 5:
+        eax5_context_commit(mEax5, dst_df);
+        break;
     }
-}
 
-void ALCcontext::eax_apply_deferred()
-{
-    if (eax_context_dirty_flags_ == ContextDirtyFlags{})
-    {
+    if(dst_df == EaxDirtyFlags{})
         return;
-    }
 
-    eax_ = eax_d_;
-
-    if (eax_context_dirty_flags_.guidPrimaryFXSlotID)
-    {
-        eax_context_shared_dirty_flags_.primary_fx_slot_id = true;
-        eax_set_primary_fx_slot_id();
-    }
+    if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
+        eax_context_commit_primary_fx_slot_id();
 
-    if (eax_context_dirty_flags_.flDistanceFactor)
-    {
-        eax_set_distance_factor();
-    }
+    if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{})
+        eax_context_commit_distance_factor();
 
-    if (eax_context_dirty_flags_.flAirAbsorptionHF)
-    {
-        eax_set_air_absorbtion_hf();
-    }
+    if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{})
+        eax_context_commit_air_absorbtion_hf();
 
-    if (eax_context_dirty_flags_.flHFReference)
-    {
-        eax_set_hf_reference();
-    }
+    if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{})
+        eax_context_commit_hf_reference();
 
-    if (eax_context_dirty_flags_.flMacroFXFactor)
-    {
-        eax_set_macro_fx_factor();
-    }
+    if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{})
+        eax_context_commit_macro_fx_factor();
 
-    if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{})
-    {
+    if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{})
         eax_update_sources();
-    }
-
-    eax_context_shared_dirty_flags_ = EaxContextSharedDirtyFlags{};
-    eax_context_dirty_flags_ = ContextDirtyFlags{};
 }
 
-
-namespace
-{
-
-
-class EaxSetException :
-    public EaxException
-{
-public:
-    explicit EaxSetException(
-        const char* message)
-        :
-        EaxException{"EAX_SET", message}
-    {
-    }
-}; // EaxSetException
-
-
-[[noreturn]]
-void eax_fail_set(
-    const char* message)
+void ALCcontext::eaxCommit()
 {
-    throw EaxSetException{message};
+    mEaxNeedsCommit = false;
+    eax_context_commit();
+    eaxCommitFxSlots();
+    eax_update_sources();
 }
 
 
-class EaxGetException :
-    public EaxException
+FORCE_ALIGN auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
+    ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
 {
-public:
-    explicit EaxGetException(
-        const char* message)
-        :
-        EaxException{"EAX_GET", message}
-    {
-    }
-}; // EaxGetException
-
-
-[[noreturn]]
-void eax_fail_get(
-    const char* message)
-{
-    throw EaxGetException{message};
+    auto context = GetContextRef();
+    if(!context) UNLIKELY return AL_INVALID_OPERATION;
+    return EAXSetDirect(context.get(), property_set_id, property_id, source_id, value, value_size);
 }
 
-
-} // namespace
-
-
-FORCE_ALIGN ALenum AL_APIENTRY EAXSet(
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_value,
-    ALuint property_value_size) noexcept
+FORCE_ALIGN auto AL_APIENTRY EAXSetDirect(ALCcontext *context, const GUID *property_set_id,
+    ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
 try
 {
-    auto context = GetContextRef();
-
-    if (!context)
-    {
-        eax_fail_set("No current context.");
-    }
-
     std::lock_guard<std::mutex> prop_lock{context->mPropLock};
-
-    return context->eax_eax_set(
-        property_set_id,
-        property_id,
-        property_source_id,
-        property_value,
-        property_value_size
-    );
+    return context->eax_eax_set(property_set_id, property_id, source_id, value, value_size);
 }
-catch (...)
+catch(...)
 {
-    eax_log_exception(__func__);
+    context->eaxSetLastError();
+    eax_log_exception(std::data(__func__));
     return AL_INVALID_OPERATION;
 }
 
-FORCE_ALIGN ALenum AL_APIENTRY EAXGet(
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_value,
-    ALuint property_value_size) noexcept
-try
+
+FORCE_ALIGN auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id,
+    ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
 {
     auto context = GetContextRef();
+    if(!context) UNLIKELY return AL_INVALID_OPERATION;
+    return EAXGetDirect(context.get(), property_set_id, property_id, source_id, value, value_size);
+}
 
-    if (!context)
-    {
-        eax_fail_get("No current context.");
-    }
-
+FORCE_ALIGN auto AL_APIENTRY EAXGetDirect(ALCcontext *context, const GUID *property_set_id,
+    ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum
+try
+{
     std::lock_guard<std::mutex> prop_lock{context->mPropLock};
-
-    return context->eax_eax_get(
-        property_set_id,
-        property_id,
-        property_source_id,
-        property_value,
-        property_value_size
-    );
+    return context->eax_eax_get(property_set_id, property_id, source_id, value, value_size);
 }
-catch (...)
+catch(...)
 {
-    eax_log_exception(__func__);
+    context->eaxSetLastError();
+    eax_log_exception(std::data(__func__));
     return AL_INVALID_OPERATION;
 }
 #endif // ALSOFT_EAX

+ 352 - 300
libs/openal-soft/alc/context.h

@@ -1,11 +1,17 @@
 #ifndef ALC_CONTEXT_H
 #define ALC_CONTEXT_H
 
+#include <array>
 #include <atomic>
+#include <deque>
 #include <memory>
 #include <mutex>
 #include <stdint.h>
+#include <string>
+#include <string_view>
+#include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include "AL/al.h"
 #include "AL/alc.h"
@@ -14,100 +20,71 @@
 #include "al/listener.h"
 #include "almalloc.h"
 #include "alnumeric.h"
+#include "althreads.h"
 #include "atomic.h"
 #include "core/context.h"
+#include "inprogext.h"
 #include "intrusive_ptr.h"
-#include "vector.h"
 
 #ifdef ALSOFT_EAX
-#include "al/eax_eax_call.h"
-#include "al/eax_fx_slot_index.h"
-#include "al/eax_fx_slots.h"
-#include "al/eax_utils.h"
-
-
-using EaxContextSharedDirtyFlagsValue = std::uint_least8_t;
-
-struct EaxContextSharedDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1;
-}; // EaxContextSharedDirtyFlags
-
-
-using ContextDirtyFlagsValue = std::uint_least8_t;
-
-struct ContextDirtyFlags
-{
-    using EaxIsBitFieldStruct = bool;
-
-    ContextDirtyFlagsValue guidPrimaryFXSlotID : 1;
-    ContextDirtyFlagsValue flDistanceFactor : 1;
-    ContextDirtyFlagsValue flAirAbsorptionHF : 1;
-    ContextDirtyFlagsValue flHFReference : 1;
-    ContextDirtyFlagsValue flMacroFXFactor : 1;
-}; // ContextDirtyFlags
-
-
-struct EaxAlIsExtensionPresentResult
-{
-    ALboolean is_present;
-    bool is_return;
-}; // EaxAlIsExtensionPresentResult
+#include "al/eax/call.h"
+#include "al/eax/exception.h"
+#include "al/eax/fx_slot_index.h"
+#include "al/eax/fx_slots.h"
+#include "al/eax/utils.h"
 #endif // ALSOFT_EAX
 
 struct ALeffect;
 struct ALeffectslot;
 struct ALsource;
+struct DebugGroup;
+struct EffectSlotSubList;
+struct SourceSubList;
 
-using uint = unsigned int;
-
+enum class DebugSource : uint8_t;
+enum class DebugType : uint8_t;
+enum class DebugSeverity : uint8_t;
 
-struct SourceSubList {
-    uint64_t FreeMask{~0_u64};
-    ALsource *Sources{nullptr}; /* 64 */
+using uint = unsigned int;
 
-    SourceSubList() noexcept = default;
-    SourceSubList(const SourceSubList&) = delete;
-    SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
-    { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
-    ~SourceSubList();
 
-    SourceSubList& operator=(const SourceSubList&) = delete;
-    SourceSubList& operator=(SourceSubList&& rhs) noexcept
-    { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
+enum ContextFlags {
+    DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */
 };
+using ContextFlagBitset = std::bitset<sizeof(ALuint)*8>;
+
 
-struct EffectSlotSubList {
-    uint64_t FreeMask{~0_u64};
-    ALeffectslot *EffectSlots{nullptr}; /* 64 */
+struct DebugLogEntry {
+    const DebugSource mSource;
+    const DebugType mType;
+    const DebugSeverity mSeverity;
+    const uint mId;
 
-    EffectSlotSubList() noexcept = default;
-    EffectSlotSubList(const EffectSlotSubList&) = delete;
-    EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
-      : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
-    { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
-    ~EffectSlotSubList();
+    std::string mMessage;
 
-    EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
-    EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
-    { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
+    template<typename T>
+    DebugLogEntry(DebugSource source, DebugType type, uint id, DebugSeverity severity, T&& message)
+        : mSource{source}, mType{type}, mSeverity{severity}, mId{id}
+        , mMessage{std::forward<T>(message)}
+    { }
+    DebugLogEntry(const DebugLogEntry&) = default;
+    DebugLogEntry(DebugLogEntry&&) = default;
 };
 
+
 struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
     const al::intrusive_ptr<ALCdevice> mALDevice;
 
-    /* Wet buffers used by effect slots. */
-    al::vector<WetBufferPtr> mWetBuffers;
-
 
     bool mPropsDirty{true};
     bool mDeferUpdates{false};
 
     std::mutex mPropLock;
 
-    std::atomic<ALenum> mLastError{AL_NO_ERROR};
+    al::tss<ALenum> mLastThreadError{AL_NO_ERROR};
+
+    const ContextFlagBitset mContextFlags;
+    std::atomic<bool> mDebugEnabled{false};
 
     DistanceModel mDistanceModel{DistanceModel::Default};
     bool mSourceDistanceModel{false};
@@ -121,23 +98,32 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
     ALEVENTPROCSOFT mEventCb{};
     void *mEventParam{nullptr};
 
+    std::mutex mDebugCbLock;
+    ALDEBUGPROCEXT mDebugCb{};
+    void *mDebugParam{nullptr};
+    std::vector<DebugGroup> mDebugGroups;
+    std::deque<DebugLogEntry> mDebugLog;
+
     ALlistener mListener{};
 
-    al::vector<SourceSubList> mSourceList;
+    std::vector<SourceSubList> mSourceList;
     ALuint mNumSources{0};
     std::mutex mSourceLock;
 
-    al::vector<EffectSlotSubList> mEffectSlotList;
+    std::vector<EffectSlotSubList> mEffectSlotList;
     ALuint mNumEffectSlots{0u};
     std::mutex mEffectSlotLock;
 
     /* Default effect slot */
     std::unique_ptr<ALeffectslot> mDefaultSlot;
 
-    const char *mExtensionList{nullptr};
+    std::vector<std::string_view> mExtensions;
+    std::string mExtensionsString{};
 
+    std::unordered_map<ALuint,std::string> mSourceNames;
+    std::unordered_map<ALuint,std::string> mEffectSlotNames;
 
-    ALCcontext(al::intrusive_ptr<ALCdevice> device);
+    ALCcontext(al::intrusive_ptr<ALCdevice> device, ContextFlagBitset flags);
     ALCcontext(const ALCcontext&) = delete;
     ALCcontext& operator=(const ALCcontext&) = delete;
     ~ALCcontext();
@@ -145,10 +131,10 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
     void init();
     /**
      * Removes the context from its device and removes it from being current on
-     * the running thread or globally. Returns true if other contexts still
-     * exist on the device.
+     * the running thread or globally. Stops device playback if this was the
+     * last context on its device.
      */
-    bool deinit();
+    void deinit();
 
     /**
      * Defers/suspends updates for the given context's listener and sources.
@@ -173,19 +159,32 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext>, ContextBase {
      */
     void applyAllUpdates();
 
-#ifdef __USE_MINGW_ANSI_STDIO
-    [[gnu::format(gnu_printf, 3, 4)]]
+#ifdef __MINGW32__
+    [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]]
 #else
     [[gnu::format(printf, 3, 4)]]
 #endif
     void setError(ALenum errorCode, const char *msg, ...);
 
+    void sendDebugMessage(std::unique_lock<std::mutex> &debuglock, DebugSource source,
+        DebugType type, ALuint id, DebugSeverity severity, std::string_view message);
+
+    void debugMessage(DebugSource source, DebugType type, ALuint id, DebugSeverity severity,
+        std::string_view message)
+    {
+        if(!mDebugEnabled.load(std::memory_order_relaxed)) LIKELY
+            return;
+        std::unique_lock<std::mutex> debuglock{mDebugCbLock};
+        sendDebugMessage(debuglock, source, type, id, severity, message);
+    }
+
     /* Process-wide current context */
+    static std::atomic<bool> sGlobalContextLock;
     static std::atomic<ALCcontext*> sGlobalContext;
 
 private:
     /* Thread-local current context. */
-    static thread_local ALCcontext *sLocalContext;
+    static inline thread_local ALCcontext *sLocalContext{};
 
     /* Thread-local context handling. This handles attempting to release the
      * context which may have been left current when the thread is destroyed.
@@ -193,37 +192,29 @@ private:
     class ThreadCtx {
     public:
         ~ThreadCtx();
+        /* NOLINTBEGIN(readability-convert-member-functions-to-static)
+         * This should be non-static to invoke construction of the thread-local
+         * sThreadContext, so that it's destructor gets run at thread exit to
+         * clear sLocalContext (which isn't a member variable to make read
+         * access efficient).
+         */
         void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; }
+        /* NOLINTEND(readability-convert-member-functions-to-static) */
     };
     static thread_local ThreadCtx sThreadContext;
 
 public:
-    /* HACK: MinGW generates bad code when accessing an extern thread_local
-     * object. Add a wrapper function for it that only accesses it where it's
-     * defined.
-     */
-#ifdef __MINGW32__
-    static ALCcontext *getThreadContext() noexcept;
-    static void setThreadContext(ALCcontext *context) noexcept;
-#else
     static ALCcontext *getThreadContext() noexcept { return sLocalContext; }
     static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); }
-#endif
 
     /* Default effect that applies to sources that don't have an effect on send 0. */
     static ALeffect sDefaultEffect;
 
-    DEF_NEWDEL(ALCcontext)
-
 #ifdef ALSOFT_EAX
-public:
-    bool has_eax() const noexcept { return eax_is_initialized_; }
-
-    bool eax_is_capable() const noexcept;
-
-
-    void eax_uninitialize() noexcept;
+    bool hasEax() const noexcept { return mEaxIsInitialized; }
+    bool eaxIsCapable() const noexcept;
 
+    void eaxUninitialize() noexcept;
 
     ALenum eax_eax_set(
         const GUID* property_set_id,
@@ -239,266 +230,327 @@ public:
         ALvoid* property_value,
         ALuint property_value_size);
 
+    void eaxSetLastError() noexcept;
 
-    void eax_update_filters();
+    EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept
+    { return mEaxPrimaryFxSlotIndex; }
 
-    void eax_commit_and_update_sources();
+    const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const
+    { return mEaxFxSlots.get(fx_slot_index); }
+    ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index)
+    { return mEaxFxSlots.get(fx_slot_index); }
 
+    bool eaxNeedsCommit() const noexcept { return mEaxNeedsCommit; }
+    void eaxCommit();
 
-    void eax_set_last_error() noexcept;
+    void eaxCommitFxSlots()
+    { mEaxFxSlots.commit(); }
 
+private:
+    static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0;
+    static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1;
+    static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2;
+    static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3;
+    static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4;
 
-    EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept
-    { return eax_previous_primary_fx_slot_index_; }
-    EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept
-    { return eax_primary_fx_slot_index_; }
+    using Eax4Props = EAX40CONTEXTPROPERTIES;
 
-    const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const
-    { return eax_fx_slots_.get(fx_slot_index); }
-    ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index)
-    { return eax_fx_slots_.get(fx_slot_index); }
+    struct Eax4State {
+        Eax4Props i; // Immediate.
+        Eax4Props d; // Deferred.
+    };
 
-    void eax_commit_fx_slots()
-    { eax_fx_slots_.commit(); }
+    using Eax5Props = EAX50CONTEXTPROPERTIES;
 
-private:
-    struct Eax
+    struct Eax5State {
+        Eax5Props i; // Immediate.
+        Eax5Props d; // Deferred.
+    };
+
+    class ContextException : public EaxException
     {
-        EAX50CONTEXTPROPERTIES context{};
-    }; // Eax
+    public:
+        explicit ContextException(const char* message)
+            : EaxException{"EAX_CONTEXT", message}
+        {}
+    };
 
+    struct Eax4PrimaryFxSlotIdValidator {
+        void operator()(const GUID& guidPrimaryFXSlotID) const
+        {
+            if(guidPrimaryFXSlotID != EAX_NULL_GUID &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot3)
+            {
+                eax_fail_unknown_primary_fx_slot_id();
+            }
+        }
+    };
 
-    bool eax_is_initialized_{};
-    bool eax_is_tried_{};
-    bool eax_are_legacy_fx_slots_unlocked_{};
+    struct Eax4DistanceFactorValidator {
+        void operator()(float flDistanceFactor) const
+        {
+            eax_validate_range<ContextException>(
+                "Distance Factor",
+                flDistanceFactor,
+                EAXCONTEXT_MINDISTANCEFACTOR,
+                EAXCONTEXT_MAXDISTANCEFACTOR);
+        }
+    };
 
-    long eax_last_error_{};
-    unsigned long eax_speaker_config_{};
+    struct Eax4AirAbsorptionHfValidator {
+        void operator()(float flAirAbsorptionHF) const
+        {
+            eax_validate_range<ContextException>(
+                "Air Absorption HF",
+                flAirAbsorptionHF,
+                EAXCONTEXT_MINAIRABSORPTIONHF,
+                EAXCONTEXT_MAXAIRABSORPTIONHF);
+        }
+    };
 
-    EaxFxSlotIndex eax_previous_primary_fx_slot_index_{};
-    EaxFxSlotIndex eax_primary_fx_slot_index_{};
-    EaxFxSlots eax_fx_slots_{};
+    struct Eax4HfReferenceValidator {
+        void operator()(float flHFReference) const
+        {
+            eax_validate_range<ContextException>(
+                "HF Reference",
+                flHFReference,
+                EAXCONTEXT_MINHFREFERENCE,
+                EAXCONTEXT_MAXHFREFERENCE);
+        }
+    };
 
-    EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{};
+    struct Eax4AllValidator {
+        void operator()(const EAX40CONTEXTPROPERTIES& all) const
+        {
+            Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID);
+            Eax4DistanceFactorValidator{}(all.flDistanceFactor);
+            Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF);
+            Eax4HfReferenceValidator{}(all.flHFReference);
+        }
+    };
 
-    Eax eax_{};
-    Eax eax_d_{};
-    EAXSESSIONPROPERTIES eax_session_{};
+    struct Eax5PrimaryFxSlotIdValidator {
+        void operator()(const GUID& guidPrimaryFXSlotID) const
+        {
+            if(guidPrimaryFXSlotID != EAX_NULL_GUID &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 &&
+                guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot3)
+            {
+                eax_fail_unknown_primary_fx_slot_id();
+            }
+        }
+    };
 
-    ContextDirtyFlags eax_context_dirty_flags_{};
+    struct Eax5MacroFxFactorValidator {
+        void operator()(float flMacroFXFactor) const
+        {
+            eax_validate_range<ContextException>(
+                "Macro FX Factor",
+                flMacroFXFactor,
+                EAXCONTEXT_MINMACROFXFACTOR,
+                EAXCONTEXT_MAXMACROFXFACTOR);
+        }
+    };
 
-    std::string eax_extension_list_{};
+    struct Eax5AllValidator {
+        void operator()(const EAX50CONTEXTPROPERTIES& all) const
+        {
+            Eax5PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID);
+            Eax4DistanceFactorValidator{}(all.flDistanceFactor);
+            Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF);
+            Eax4HfReferenceValidator{}(all.flHFReference);
+            Eax5MacroFxFactorValidator{}(all.flMacroFXFactor);
+        }
+    };
 
+    struct Eax5EaxVersionValidator {
+        void operator()(unsigned long ulEAXVersion) const
+        {
+            eax_validate_range<ContextException>(
+                "EAX version",
+                ulEAXVersion,
+                EAXCONTEXT_MINEAXSESSION,
+                EAXCONTEXT_MAXEAXSESSION);
+        }
+    };
 
-    [[noreturn]]
-    static void eax_fail(
-        const char* message);
+    struct Eax5MaxActiveSendsValidator {
+        void operator()(unsigned long ulMaxActiveSends) const
+        {
+            eax_validate_range<ContextException>(
+                "Max Active Sends",
+                ulMaxActiveSends,
+                EAXCONTEXT_MINMAXACTIVESENDS,
+                EAXCONTEXT_MAXMAXACTIVESENDS);
+        }
+    };
 
+    struct Eax5SessionAllValidator {
+        void operator()(const EAXSESSIONPROPERTIES& all) const
+        {
+            Eax5EaxVersionValidator{}(all.ulEAXVersion);
+            Eax5MaxActiveSendsValidator{}(all.ulMaxActiveSends);
+        }
+    };
 
-    void eax_initialize_extensions();
+    struct Eax5SpeakerConfigValidator {
+        void operator()(unsigned long ulSpeakerConfig) const
+        {
+            eax_validate_range<ContextException>(
+                "Speaker Config",
+                ulSpeakerConfig,
+                EAXCONTEXT_MINSPEAKERCONFIG,
+                EAXCONTEXT_MAXSPEAKERCONFIG);
+        }
+    };
 
-    void eax_initialize();
+    bool mEaxIsInitialized{};
+    bool mEaxIsTried{};
+
+    long mEaxLastError{};
+    unsigned long mEaxSpeakerConfig{};
+
+    EaxFxSlotIndex mEaxPrimaryFxSlotIndex{};
+    EaxFxSlots mEaxFxSlots{};
+
+    int mEaxVersion{}; // Current EAX version.
+    bool mEaxNeedsCommit{};
+    EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version.
+    Eax5State mEax123{}; // EAX1/EAX2/EAX3 state.
+    Eax4State mEax4{}; // EAX4 state.
+    Eax5State mEax5{}; // EAX5 state.
+    Eax5Props mEax{}; // Current EAX state.
+    EAXSESSIONPROPERTIES mEaxSession{};
+
+    [[noreturn]] static void eax_fail(const char* message);
+    [[noreturn]] static void eax_fail_unknown_property_set_id();
+    [[noreturn]] static void eax_fail_unknown_primary_fx_slot_id();
+    [[noreturn]] static void eax_fail_unknown_property_id();
+    [[noreturn]] static void eax_fail_unknown_version();
+
+    // Gets a value from EAX call,
+    // validates it,
+    // and updates the current value.
+    template<typename TValidator, typename TProperty>
+    static void eax_set(const EaxCall& call, TProperty& property)
+    {
+        const auto& value = call.get_value<ContextException, const TProperty>();
+        TValidator{}(value);
+        property = value;
+    }
 
+    // Gets a new value from EAX call,
+    // validates it,
+    // updates the deferred value,
+    // updates a dirty flag.
+    template<
+        typename TValidator,
+        EaxDirtyFlags TDirtyBit,
+        typename TMemberResult,
+        typename TProps,
+        typename TState>
+    void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member)
+    {
+        const auto& src = call.get_value<ContextException, const TMemberResult>();
+        TValidator{}(src);
+        const auto& dst_i = state.i.*member;
+        auto& dst_d = state.d.*member;
+        dst_d = src;
+
+        if(dst_i != dst_d)
+            mEaxDf |= TDirtyBit;
+    }
 
-    bool eax_has_no_default_effect_slot() const noexcept;
+    template<
+        EaxDirtyFlags TDirtyBit,
+        typename TMemberResult,
+        typename TProps,
+        typename TState>
+    void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df,
+        TMemberResult TProps::*member) noexcept
+    {
+        if((mEaxDf & TDirtyBit) != EaxDirtyFlags{})
+        {
+            dst_df |= TDirtyBit;
+            const auto& src_d = state.d.*member;
+            state.i.*member = src_d;
+            mEax.*member = src_d;
+        }
+    }
 
-    void eax_ensure_no_default_effect_slot() const;
+    void eax_initialize_extensions();
+    void eax_initialize();
 
+    bool eax_has_no_default_effect_slot() const noexcept;
+    void eax_ensure_no_default_effect_slot() const;
     bool eax_has_enough_aux_sends() const noexcept;
-
     void eax_ensure_enough_aux_sends() const;
-
     void eax_ensure_compatibility();
 
-
     unsigned long eax_detect_speaker_configuration() const;
     void eax_update_speaker_configuration();
 
-
     void eax_set_last_error_defaults() noexcept;
-
-    void eax_set_session_defaults() noexcept;
-
-    void eax_set_context_defaults() noexcept;
-
-    void eax_set_defaults() noexcept;
-
-    void eax_initialize_sources();
-
-
-    void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept;
-
-
-    void eax_dispatch_fx_slot(
-        const EaxEaxCall& eax_call);
-
-    void eax_dispatch_source(
-        const EaxEaxCall& eax_call);
-
-
-    void eax_get_primary_fx_slot_id(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_distance_factor(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_air_absorption_hf(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_hf_reference(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_last_error(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_speaker_config(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_session(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_macro_fx_factor(
-        const EaxEaxCall& eax_call);
-
-    void eax_get_context_all(
-        const EaxEaxCall& eax_call);
-
-    void eax_get(
-        const EaxEaxCall& eax_call);
-
-
-    void eax_set_primary_fx_slot_id();
-
-    void eax_set_distance_factor();
-
-    void eax_set_air_absorbtion_hf();
-
-    void eax_set_hf_reference();
-
-    void eax_set_macro_fx_factor();
-
-    void eax_set_context();
+    void eax_session_set_defaults() noexcept;
+    static void eax4_context_set_defaults(Eax4Props& props) noexcept;
+    static void eax4_context_set_defaults(Eax4State& state) noexcept;
+    static void eax5_context_set_defaults(Eax5Props& props) noexcept;
+    static void eax5_context_set_defaults(Eax5State& state) noexcept;
+    void eax_context_set_defaults();
+    void eax_set_defaults();
+
+    void eax_dispatch_fx_slot(const EaxCall& call);
+    void eax_dispatch_source(const EaxCall& call);
+
+    void eax_get_misc(const EaxCall& call);
+    void eax4_get(const EaxCall& call, const Eax4Props& props);
+    void eax5_get(const EaxCall& call, const Eax5Props& props);
+    void eax_get(const EaxCall& call);
+
+    void eax_context_commit_primary_fx_slot_id();
+    void eax_context_commit_distance_factor();
+    void eax_context_commit_air_absorbtion_hf();
+    void eax_context_commit_hf_reference();
+    void eax_context_commit_macro_fx_factor();
 
     void eax_initialize_fx_slots();
 
-
     void eax_update_sources();
 
+    void eax_set_misc(const EaxCall& call);
+    void eax4_defer_all(const EaxCall& call, Eax4State& state);
+    void eax4_defer(const EaxCall& call, Eax4State& state);
+    void eax5_defer_all(const EaxCall& call, Eax5State& state);
+    void eax5_defer(const EaxCall& call, Eax5State& state);
+    void eax_set(const EaxCall& call);
 
-    void eax_validate_primary_fx_slot_id(
-        const GUID& primary_fx_slot_id);
-
-    void eax_validate_distance_factor(
-        float distance_factor);
-
-    void eax_validate_air_absorption_hf(
-        float air_absorption_hf);
-
-    void eax_validate_hf_reference(
-        float hf_reference);
-
-    void eax_validate_speaker_config(
-        unsigned long speaker_config);
-
-    void eax_validate_session_eax_version(
-        unsigned long eax_version);
-
-    void eax_validate_session_max_active_sends(
-        unsigned long max_active_sends);
-
-    void eax_validate_session(
-        const EAXSESSIONPROPERTIES& eax_session);
-
-    void eax_validate_macro_fx_factor(
-        float macro_fx_factor);
-
-    void eax_validate_context_all(
-        const EAX40CONTEXTPROPERTIES& context_all);
-
-    void eax_validate_context_all(
-        const EAX50CONTEXTPROPERTIES& context_all);
-
-
-    void eax_defer_primary_fx_slot_id(
-        const GUID& primary_fx_slot_id);
-
-    void eax_defer_distance_factor(
-        float distance_factor);
-
-    void eax_defer_air_absorption_hf(
-        float air_absorption_hf);
-
-    void eax_defer_hf_reference(
-        float hf_reference);
-
-    void eax_defer_macro_fx_factor(
-        float macro_fx_factor);
-
-    void eax_defer_context_all(
-        const EAX40CONTEXTPROPERTIES& context_all);
-
-    void eax_defer_context_all(
-        const EAX50CONTEXTPROPERTIES& context_all);
-
-
-    void eax_defer_context_all(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_primary_fx_slot_id(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_distance_factor(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_air_absorption_hf(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_hf_reference(
-        const EaxEaxCall& eax_call);
-
-    void eax_set_session(
-        const EaxEaxCall& eax_call);
-
-    void eax_defer_macro_fx_factor(
-        const EaxEaxCall& eax_call);
-
-    void eax_set(
-        const EaxEaxCall& eax_call);
-
-    void eax_apply_deferred();
+    void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df);
+    void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df);
+    void eax_context_commit();
 #endif // ALSOFT_EAX
 };
 
-#define SETERR_RETURN(ctx, err, retval, ...) do {                             \
-    (ctx)->setError((err), __VA_ARGS__);                                      \
-    return retval;                                                            \
-} while(0)
-
-
 using ContextRef = al::intrusive_ptr<ALCcontext>;
 
-ContextRef GetContextRef(void);
+ContextRef GetContextRef() noexcept;
 
 void UpdateContextProps(ALCcontext *context);
 
 
-extern bool TrapALError;
+inline bool TrapALError{false};
 
 
 #ifdef ALSOFT_EAX
-ALenum AL_APIENTRY EAXSet(
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_value,
-    ALuint property_value_size) noexcept;
-
-ALenum AL_APIENTRY EAXGet(
-    const GUID* property_set_id,
-    ALuint property_id,
-    ALuint property_source_id,
-    ALvoid* property_value,
-    ALuint property_value_size) noexcept;
+auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id,
+    ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;
+
+auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id,
+    ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum;
 #endif // ALSOFT_EAX
 
 #endif /* ALC_CONTEXT_H */

部分文件因为文件数量过多而无法显示