浏览代码

Update OpenAL-Soft to 1.22.0

Alex Szpakowski 3 年之前
父节点
当前提交
2b15394df5
共有 100 个文件被更改,包括 14701 次插入4325 次删除
  1. 10 1
      CMakeLists.txt
  2. 2 1
      libs/openal-soft/.travis.yml
  3. 417 346
      libs/openal-soft/Alc/alc.cpp
  4. 68 82
      libs/openal-soft/Alc/alconfig.cpp
  5. 1 3
      libs/openal-soft/Alc/alconfig.h
  6. 0 282
      libs/openal-soft/Alc/alcontext.h
  7. 279 284
      libs/openal-soft/Alc/alu.cpp
  8. 11 114
      libs/openal-soft/Alc/alu.h
  9. 86 85
      libs/openal-soft/Alc/backends/alsa.cpp
  10. 2 2
      libs/openal-soft/Alc/backends/alsa.h
  11. 16 17
      libs/openal-soft/Alc/backends/base.cpp
  12. 12 8
      libs/openal-soft/Alc/backends/base.h
  13. 378 120
      libs/openal-soft/Alc/backends/coreaudio.cpp
  14. 2 2
      libs/openal-soft/Alc/backends/coreaudio.h
  15. 39 64
      libs/openal-soft/Alc/backends/dsound.cpp
  16. 2 2
      libs/openal-soft/Alc/backends/dsound.h
  17. 263 125
      libs/openal-soft/Alc/backends/jack.cpp
  18. 2 2
      libs/openal-soft/Alc/backends/jack.h
  19. 6 7
      libs/openal-soft/Alc/backends/loopback.cpp
  20. 2 2
      libs/openal-soft/Alc/backends/loopback.h
  21. 5 5
      libs/openal-soft/Alc/backends/null.cpp
  22. 2 2
      libs/openal-soft/Alc/backends/null.h
  23. 55 54
      libs/openal-soft/Alc/backends/oboe.cpp
  24. 2 2
      libs/openal-soft/Alc/backends/oboe.h
  25. 23 14
      libs/openal-soft/Alc/backends/opensl.cpp
  26. 2 2
      libs/openal-soft/Alc/backends/opensl.h
  27. 14 10
      libs/openal-soft/Alc/backends/oss.cpp
  28. 2 2
      libs/openal-soft/Alc/backends/oss.h
  29. 2008 0
      libs/openal-soft/Alc/backends/pipewire.cpp
  30. 23 0
      libs/openal-soft/Alc/backends/pipewire.h
  31. 28 23
      libs/openal-soft/Alc/backends/portaudio.cpp
  32. 2 2
      libs/openal-soft/Alc/backends/portaudio.h
  33. 78 131
      libs/openal-soft/Alc/backends/pulseaudio.cpp
  34. 2 2
      libs/openal-soft/Alc/backends/pulseaudio.h
  35. 37 32
      libs/openal-soft/Alc/backends/sdl2.cpp
  36. 2 2
      libs/openal-soft/Alc/backends/sdl2.h
  37. 160 144
      libs/openal-soft/Alc/backends/sndio.cpp
  38. 2 2
      libs/openal-soft/Alc/backends/sndio.h
  39. 27 23
      libs/openal-soft/Alc/backends/solaris.cpp
  40. 2 2
      libs/openal-soft/Alc/backends/solaris.h
  41. 214 248
      libs/openal-soft/Alc/backends/wasapi.cpp
  42. 2 2
      libs/openal-soft/Alc/backends/wasapi.h
  43. 24 29
      libs/openal-soft/Alc/backends/wave.cpp
  44. 2 2
      libs/openal-soft/Alc/backends/wave.h
  45. 39 37
      libs/openal-soft/Alc/backends/winmm.cpp
  46. 2 2
      libs/openal-soft/Alc/backends/winmm.h
  47. 0 9
      libs/openal-soft/Alc/compat.h
  48. 1299 0
      libs/openal-soft/Alc/context.cpp
  49. 504 0
      libs/openal-soft/Alc/context.h
  50. 90 0
      libs/openal-soft/Alc/device.cpp
  51. 165 0
      libs/openal-soft/Alc/device.h
  52. 25 15
      libs/openal-soft/Alc/effects/autowah.cpp
  53. 1 187
      libs/openal-soft/Alc/effects/base.h
  54. 22 16
      libs/openal-soft/Alc/effects/chorus.cpp
  55. 47 23
      libs/openal-soft/Alc/effects/compressor.cpp
  56. 66 21
      libs/openal-soft/Alc/effects/convolution.cpp
  57. 20 10
      libs/openal-soft/Alc/effects/dedicated.cpp
  58. 22 11
      libs/openal-soft/Alc/effects/distortion.cpp
  59. 25 13
      libs/openal-soft/Alc/effects/echo.cpp
  60. 21 13
      libs/openal-soft/Alc/effects/equalizer.cpp
  61. 25 15
      libs/openal-soft/Alc/effects/fshifter.cpp
  62. 25 15
      libs/openal-soft/Alc/effects/modulator.cpp
  63. 13 8
      libs/openal-soft/Alc/effects/null.cpp
  64. 26 16
      libs/openal-soft/Alc/effects/pshifter.cpp
  65. 95 76
      libs/openal-soft/Alc/effects/reverb.cpp
  66. 53 30
      libs/openal-soft/Alc/effects/vmorpher.cpp
  67. 17 17
      libs/openal-soft/Alc/inprogext.h
  68. 425 394
      libs/openal-soft/Alc/panning.cpp
  69. 0 784
      libs/openal-soft/Alc/voice.cpp
  70. 1 0
      libs/openal-soft/BSD-3Clause
  71. 250 146
      libs/openal-soft/CMakeLists.txt
  72. 54 0
      libs/openal-soft/ChangeLog
  73. 9 0
      libs/openal-soft/OpenALConfig.cmake.in
  74. 832 34
      libs/openal-soft/al/auxeffectslot.cpp
  75. 214 5
      libs/openal-soft/al/auxeffectslot.h
  76. 352 122
      libs/openal-soft/al/buffer.cpp
  77. 13 2
      libs/openal-soft/al/buffer.h
  78. 1213 0
      libs/openal-soft/al/eax_api.cpp
  79. 1557 0
      libs/openal-soft/al/eax_api.h
  80. 324 0
      libs/openal-soft/al/eax_eax_call.cpp
  81. 117 0
      libs/openal-soft/al/eax_eax_call.h
  82. 3 0
      libs/openal-soft/al/eax_effect.cpp
  83. 44 0
      libs/openal-soft/al/eax_effect.h
  84. 63 0
      libs/openal-soft/al/eax_exception.cpp
  85. 25 0
      libs/openal-soft/al/eax_exception.h
  86. 71 0
      libs/openal-soft/al/eax_fx_slot_index.cpp
  87. 41 0
      libs/openal-soft/al/eax_fx_slot_index.h
  88. 84 0
      libs/openal-soft/al/eax_fx_slots.cpp
  89. 54 0
      libs/openal-soft/al/eax_fx_slots.h
  90. 21 0
      libs/openal-soft/al/eax_globals.cpp
  91. 22 0
      libs/openal-soft/al/eax_globals.h
  92. 36 0
      libs/openal-soft/al/eax_utils.cpp
  93. 132 0
      libs/openal-soft/al/eax_utils.h
  94. 3 0
      libs/openal-soft/al/eax_x_ram.cpp
  95. 38 0
      libs/openal-soft/al/eax_x_ram.h
  96. 36 17
      libs/openal-soft/al/effect.cpp
  97. 2 0
      libs/openal-soft/al/effect.h
  98. 440 1
      libs/openal-soft/al/effects/autowah.cpp
  99. 1082 1
      libs/openal-soft/al/effects/chorus.cpp
  100. 225 1
      libs/openal-soft/al/effects/compressor.cpp

+ 10 - 1
CMakeLists.txt

@@ -204,7 +204,7 @@ set(MEGA_LIBVORBIS_VER "1.3.5")
 set(MEGA_LIBTHEORA_VER "1.1.1")
 set(MEGA_FREETYPE_VER "2.12.0")
 set(MEGA_SDL2_VER "2.0.18")
-set(MEGA_OPENAL_VER "1.21.1")
+set(MEGA_OPENAL_VER "1.22.0")
 set(MEGA_MODPLUG_VER "0.8.8.4")
 
 set(SKIP_INSTALL_ALL TRUE)
@@ -269,6 +269,15 @@ set(MEGA_SDL2MAIN SDL2main)
 message(STATUS "-----------------------------------------------------")
 message(STATUS "Configuring: openal-soft ${MEGA_OPENAL_VER}")
 message(STATUS "-----------------------------------------------------")
+set(ALSOFT_UTILS OFF CACHE BOOL "Build utility programs" FORCE)
+set(ALSOFT_NO_CONFIG_UTIL ON CACHE BOOL "Disable building the alsoft-config utility" FORCE)
+set(ALSOFT_EXAMPLES OFF CACHE BOOL "Build example programs" FORCE)
+set(ALSOFT_INSTALL OFF CACHE BOOL "Install main library" FORCE)
+set(ALSOFT_INSTALL_CONFIG OFF CACHE BOOL "Install alsoft.conf sample configuration file" FORCE)
+set(ALSOFT_INSTALL_HRTF_DATA OFF CACHE BOOL "Install HRTF data files" FORCE)
+set(ALSOFT_INSTALL_AMBDEC_PRESETS OFF CACHE BOOL "Install AmbDec preset files" FORCE)
+set(ALSOFT_INSTALL_EXAMPLES OFF CACHE BOOL "Install example programs (alplay, alstream, ...)" FORCE)
+set(ALSOFT_INSTALL_UTILS OFF CACHE BOOL "Install utility programs (openal-info, alsoft-config, ...)" FORCE)
 if(ANDROID)
 	set(OBOE_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/libs/oboe" CACHE PATH "Oboe Source Code" FORCE)
 	set(ALSOFT_REQUIRE_OBOE ON CACHE BOOL "Require Oboe backend" FORCE)

+ 2 - 1
libs/openal-soft/.travis.yml

@@ -26,7 +26,8 @@ install:
         portaudio19-dev \
         libasound2-dev \
         libjack-dev \
-        qtbase5-dev
+        qtbase5-dev \
+        libdbus-1-dev
     fi
   - >
     if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then

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


+ 68 - 82
libs/openal-soft/Alc/alconfig.cpp

@@ -40,7 +40,7 @@
 
 #include "alfstream.h"
 #include "alstring.h"
-#include "compat.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "strutils.h"
 #include "vector.h"
@@ -277,6 +277,52 @@ void LoadConfigFromFile(std::istream &f)
     ConfOpts.shrink_to_fit();
 }
 
+const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
+{
+    if(!keyName)
+        return nullptr;
+
+    std::string key;
+    if(blockName && al::strcasecmp(blockName, "general") != 0)
+    {
+        key = blockName;
+        if(devName)
+        {
+            key += '/';
+            key += devName;
+        }
+        key += '/';
+        key += keyName;
+    }
+    else
+    {
+        if(devName)
+        {
+            key = devName;
+            key += '/';
+        }
+        key += keyName;
+    }
+
+    auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
+        [&key](const ConfigEntry &entry) -> bool
+        { return entry.key == key; });
+    if(iter != ConfOpts.cend())
+    {
+        TRACE("Found %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());
+        return nullptr;
+    }
+    return GetConfigValue(nullptr, blockName, keyName);
+}
+
 } // namespace
 
 
@@ -437,106 +483,46 @@ void ReadALConfig()
 }
 #endif
 
-const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
-{
-    if(!keyName)
-        return def;
-
-    std::string key;
-    if(blockName && al::strcasecmp(blockName, "general") != 0)
-    {
-        key = blockName;
-        if(devName)
-        {
-            key += '/';
-            key += devName;
-        }
-        key += '/';
-        key += keyName;
-    }
-    else
-    {
-        if(devName)
-        {
-            key = devName;
-            key += '/';
-        }
-        key += keyName;
-    }
-
-    auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
-        [&key](const ConfigEntry &entry) -> bool
-        { return entry.key == key; }
-    );
-    if(iter != ConfOpts.cend())
-    {
-        TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
-        if(!iter->value.empty())
-            return iter->value.c_str();
-        return def;
-    }
-
-    if(!devName)
-    {
-        TRACE("Key %s not found\n", key.c_str());
-        return def;
-    }
-    return GetConfigValue(nullptr, blockName, keyName, def);
-}
-
-int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    return val[0] != 0;
-}
-
 al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return al::nullopt;
-
-    return al::make_optional<std::string>(val);
+    if(const char *val{GetConfigValue(devName, blockName, keyName)})
+        return al::make_optional<std::string>(val);
+    return al::nullopt;
 }
 
 al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return al::nullopt;
-
-    return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
+    if(const char *val{GetConfigValue(devName, blockName, keyName)})
+        return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
+    return al::nullopt;
 }
 
 al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return al::nullopt;
-
-    return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
+    if(const char *val{GetConfigValue(devName, blockName, keyName)})
+        return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
+    return al::nullopt;
 }
 
 al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return al::nullopt;
-
-    return al::make_optional(std::strtof(val, nullptr));
+    if(const char *val{GetConfigValue(devName, blockName, keyName)})
+        return al::make_optional(std::strtof(val, nullptr));
+    return al::nullopt;
 }
 
 al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return al::nullopt;
-
-    return al::make_optional(
-        al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
-        al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
+    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;
 }
 
-int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
+bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
 {
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-
-    if(!val[0]) return def != 0;
-    return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
-            al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
+    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 def;
 }

+ 1 - 3
libs/openal-soft/Alc/alconfig.h

@@ -7,9 +7,7 @@
 
 void ReadALConfig();
 
-int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
-const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
-int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
+bool GetConfigValueBool(const char *devName, const char *blockName, const char *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);

+ 0 - 282
libs/openal-soft/Alc/alcontext.h

@@ -1,282 +0,0 @@
-#ifndef ALCONTEXT_H
-#define ALCONTEXT_H
-
-#include <array>
-#include <atomic>
-#include <cstddef>
-#include <cstdint>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <utility>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-
-#include "al/listener.h"
-#include "almalloc.h"
-#include "alnumeric.h"
-#include "alu.h"
-#include "atomic.h"
-#include "inprogext.h"
-#include "intrusive_ptr.h"
-#include "threads.h"
-#include "vecmat.h"
-#include "vector.h"
-
-struct ALeffectslot;
-struct ALsource;
-struct EffectSlot;
-struct EffectSlotProps;
-struct RingBuffer;
-struct Voice;
-struct VoiceChange;
-struct VoicePropsItem;
-
-
-enum class DistanceModel : unsigned char {
-    Disable,
-    Inverse, InverseClamped,
-    Linear, LinearClamped,
-    Exponent, ExponentClamped,
-
-    Default = InverseClamped
-};
-
-
-struct WetBuffer {
-    bool mInUse;
-    al::FlexArray<FloatBufferLine, 16> mBuffer;
-
-    WetBuffer(size_t count) : mBuffer{count} { }
-
-    DEF_FAM_NEWDEL(WetBuffer, mBuffer)
-};
-using WetBufferPtr = std::unique_ptr<WetBuffer>;
-
-
-struct ContextProps {
-    float DopplerFactor;
-    float DopplerVelocity;
-    float SpeedOfSound;
-    bool SourceDistanceModel;
-    DistanceModel mDistanceModel;
-
-    std::atomic<ContextProps*> next;
-
-    DEF_NEWDEL(ContextProps)
-};
-
-struct ListenerProps {
-    std::array<float,3> Position;
-    std::array<float,3> Velocity;
-    std::array<float,3> OrientAt;
-    std::array<float,3> OrientUp;
-    float Gain;
-    float MetersPerUnit;
-
-    std::atomic<ListenerProps*> next;
-
-    DEF_NEWDEL(ListenerProps)
-};
-
-struct ContextParams {
-    /* Pointer to the most recent property values that are awaiting an update. */
-    std::atomic<ContextProps*> ContextUpdate{nullptr};
-    std::atomic<ListenerProps*> ListenerUpdate{nullptr};
-
-    alu::Matrix Matrix{alu::Matrix::Identity()};
-    alu::Vector Velocity{};
-
-    float Gain{1.0f};
-    float MetersPerUnit{1.0f};
-
-    float DopplerFactor{1.0f};
-    float SpeedOfSound{343.3f}; /* in units per sec! */
-
-    bool SourceDistanceModel{false};
-    DistanceModel mDistanceModel{};
-};
-
-
-struct SourceSubList {
-    uint64_t FreeMask{~0_u64};
-    ALsource *Sources{nullptr}; /* 64 */
-
-    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; }
-};
-
-struct EffectSlotSubList {
-    uint64_t FreeMask{~0_u64};
-    ALeffectslot *EffectSlots{nullptr}; /* 64 */
-
-    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; }
-};
-
-struct ALCcontext : public al::intrusive_ref<ALCcontext> {
-    const al::intrusive_ptr<ALCdevice> mDevice;
-
-    /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
-     * indicates if updates are currently happening).
-     */
-    RefCount mUpdateCount{0u};
-    std::atomic<bool> mHoldUpdates{false};
-
-    float mGainBoost{1.0f};
-
-    /* Linked lists of unused property containers, free to use for future
-     * updates.
-     */
-    std::atomic<ContextProps*> mFreeContextProps{nullptr};
-    std::atomic<ListenerProps*> mFreeListenerProps{nullptr};
-    std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
-    std::atomic<EffectSlotProps*> mFreeEffectslotProps{nullptr};
-
-    /* The voice change tail is the beginning of the "free" elements, up to and
-     * *excluding* the current. If tail==current, there's no free elements and
-     * new ones need to be allocated. The current voice change is the element
-     * last processed, and any after are pending.
-     */
-    VoiceChange *mVoiceChangeTail{};
-    std::atomic<VoiceChange*> mCurrentVoiceChange{};
-
-    void allocVoiceChanges(size_t addcount);
-
-
-    ContextParams mParams;
-
-    using VoiceArray = al::FlexArray<Voice*>;
-    std::atomic<VoiceArray*> mVoices{};
-    std::atomic<size_t> mActiveVoiceCount{};
-
-    void allocVoices(size_t addcount);
-    al::span<Voice*> getVoicesSpan() const noexcept
-    {
-        return {mVoices.load(std::memory_order_relaxed)->data(),
-            mActiveVoiceCount.load(std::memory_order_relaxed)};
-    }
-    al::span<Voice*> getVoicesSpanAcquired() const noexcept
-    {
-        return {mVoices.load(std::memory_order_acquire)->data(),
-            mActiveVoiceCount.load(std::memory_order_acquire)};
-    }
-
-
-    using EffectSlotArray = al::FlexArray<EffectSlot*>;
-    std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};
-
-    std::thread mEventThread;
-    al::semaphore mEventSem;
-    std::unique_ptr<RingBuffer> mAsyncEvents;
-    std::atomic<uint> mEnabledEvts{0u};
-
-    /* Asynchronous voice change actions are processed as a linked list of
-     * VoiceChange objects by the mixer, which is atomically appended to.
-     * However, to avoid allocating each object individually, they're allocated
-     * in clusters that are stored in a vector for easy automatic cleanup.
-     */
-    using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
-    al::vector<VoiceChangeCluster> mVoiceChangeClusters;
-
-    using VoiceCluster = std::unique_ptr<Voice[]>;
-    al::vector<VoiceCluster> mVoiceClusters;
-
-    /* Wet buffers used by effect slots. */
-    al::vector<WetBufferPtr> mWetBuffers;
-
-
-    std::atomic_flag mPropsClean;
-    std::atomic<bool> mDeferUpdates{false};
-
-    std::mutex mPropLock;
-
-    std::atomic<ALenum> mLastError{AL_NO_ERROR};
-
-    DistanceModel mDistanceModel{DistanceModel::Default};
-    bool mSourceDistanceModel{false};
-
-    float mDopplerFactor{1.0f};
-    float mDopplerVelocity{1.0f};
-    float mSpeedOfSound{SpeedOfSoundMetersPerSec};
-
-    std::mutex mEventCbLock;
-    ALEVENTPROCSOFT mEventCb{};
-    void *mEventParam{nullptr};
-
-    ALlistener mListener{};
-
-    al::vector<SourceSubList> mSourceList;
-    ALuint mNumSources{0};
-    std::mutex mSourceLock;
-
-    al::vector<EffectSlotSubList> mEffectSlotList;
-    ALuint mNumEffectSlots{0u};
-    std::mutex mEffectSlotLock;
-
-    /* Default effect slot */
-    std::unique_ptr<ALeffectslot> mDefaultSlot;
-
-    const char *mExtensionList{nullptr};
-
-
-    ALCcontext(al::intrusive_ptr<ALCdevice> device);
-    ALCcontext(const ALCcontext&) = delete;
-    ALCcontext& operator=(const ALCcontext&) = delete;
-    ~ALCcontext();
-
-    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.
-     */
-    bool deinit();
-
-    /**
-     * Defers/suspends updates for the given context's listener and sources.
-     * This does *NOT* stop mixing, but rather prevents certain property
-     * changes from taking effect.
-     */
-    void deferUpdates() noexcept { mDeferUpdates.exchange(true, std::memory_order_acq_rel); }
-
-    /** Resumes update processing after being deferred. */
-    void processUpdates();
-
-    [[gnu::format(printf,3,4)]] void setError(ALenum errorCode, const char *msg, ...);
-
-    DEF_NEWDEL(ALCcontext)
-};
-
-#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);
-
-void UpdateContextProps(ALCcontext *context);
-
-
-extern bool TrapALError;
-
-#endif /* ALCONTEXT_H */

文件差异内容过多而无法显示
+ 279 - 284
libs/openal-soft/Alc/alu.cpp


+ 11 - 114
libs/openal-soft/Alc/alu.h

@@ -1,141 +1,38 @@
 #ifndef ALU_H
 #define ALU_H
 
-#include <array>
-#include <cmath>
-#include <cstddef>
-#include <type_traits>
+#include <bitset>
 
-#include "alspan.h"
-#include "core/ambidefs.h"
-#include "core/bufferline.h"
-#include "core/devformat.h"
+#include "aloptional.h"
 
 struct ALCcontext;
 struct ALCdevice;
 struct EffectSlot;
-struct MixParams;
 
-
-#define MAX_SENDS  6
-
-
-using MixerFunc = void(*)(const al::span<const float> InSamples,
-    const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
-    const size_t Counter, const size_t OutPos);
-
-extern MixerFunc MixSamples;
+enum class StereoEncoding : unsigned char;
 
 
 constexpr float GainMixMax{1000.0f}; /* +60dB */
 
-constexpr float SpeedOfSoundMetersPerSec{343.3f};
-constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */
 
-/** Target gain for the reverb decay feedback reaching the decay time. */
-constexpr float ReverbDecayGain{0.001f}; /* -60 dB */
+enum CompatFlags : uint8_t {
+    ReverseX,
+    ReverseY,
+    ReverseZ,
 
-
-enum HrtfRequestMode {
-    Hrtf_Default = 0,
-    Hrtf_Enable = 1,
-    Hrtf_Disable = 2,
+    Count
 };
+using CompatFlagBitset = std::bitset<CompatFlags::Count>;
 
-void aluInit(void);
-
-void aluInitMixer(void);
+void aluInit(CompatFlagBitset flags);
 
 /* aluInitRenderer
  *
  * Set up the appropriate panning method and mixing method given the device
  * properties.
  */
-void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq,
-    HrtfRequestMode hrtf_userreq);
+void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode);
 
 void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
 
-/**
- * Calculates ambisonic encoder coefficients using the X, Y, and Z direction
- * components, which must represent a normalized (unit length) vector, and the
- * spread is the angular width of the sound (0...tau).
- *
- * NOTE: The components use ambisonic coordinates. As a result:
- *
- * Ambisonic Y = OpenAL -X
- * Ambisonic Z = OpenAL Y
- * Ambisonic X = OpenAL -Z
- *
- * The components are ordered such that OpenAL's X, Y, and Z are the first,
- * second, and third parameters respectively -- simply negate X and Z.
- */
-std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
-    const float spread);
-
-/**
- * CalcDirectionCoeffs
- *
- * Calculates ambisonic coefficients based on an OpenAL direction vector. The
- * vector must be normalized (unit length), and the spread is the angular width
- * of the sound (0...tau).
- */
-inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[3],
-    const float spread)
-{
-    /* Convert from OpenAL coords to Ambisonics. */
-    return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread);
-}
-
-/**
- * CalcAngleCoeffs
- *
- * Calculates ambisonic coefficients based on azimuth and elevation. The
- * azimuth and elevation parameters are in radians, going right and up
- * respectively.
- */
-inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth,
-    const float elevation, const float spread)
-{
-    const float x{-std::sin(azimuth) * std::cos(elevation)};
-    const float y{ std::sin(elevation)};
-    const float z{ std::cos(azimuth) * std::cos(elevation)};
-
-    return CalcAmbiCoeffs(x, y, z, spread);
-}
-
-
-/**
- * ComputePanGains
- *
- * Computes panning gains using the given channel decoder coefficients and the
- * pre-calculated direction or angle coefficients. For B-Format sources, the
- * coeffs are a 'slice' of a transform matrix for the input channel, used to
- * scale and orient the sound samples.
- */
-void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
-    const al::span<float,MAX_OUTPUT_CHANNELS> gains);
-
-
-/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */
-template<typename T, typename I, typename F>
-auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t<std::is_integral<I>::value>
-{
-    if(count < 1) return;
-
-    std::array<float,MaxAmbiChannels> coeffs{{1.0f}};
-    func(*iter, coeffs);
-    ++iter;
-    for(I i{1};i < count;++i,++iter)
-    {
-        coeffs[i-1] = 0.0f;
-        coeffs[i  ] = 1.0f;
-        func(*iter, coeffs);
-    }
-}
-
-
-extern const float ConeScale;
-extern const float ZScale;
-
 #endif

+ 86 - 85
libs/openal-soft/Alc/backends/alsa.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/alsa.h"
+#include "alsa.h"
 
 #include <algorithm>
 #include <atomic>
@@ -36,12 +36,12 @@
 #include <utility>
 
 #include "albyte.h"
-#include "alcmain.h"
-#include "alconfig.h"
+#include "alc/alconfig.h"
 #include "almalloc.h"
 #include "alnumeric.h"
 #include "aloptional.h"
-#include "alu.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "dynload.h"
 #include "ringbuffer.h"
@@ -68,35 +68,37 @@ constexpr char alsaDevice[] = "ALSA Default";
     MAGIC(snd_pcm_hw_params_free);                                            \
     MAGIC(snd_pcm_hw_params_any);                                             \
     MAGIC(snd_pcm_hw_params_current);                                         \
+    MAGIC(snd_pcm_hw_params_get_access);                                      \
+    MAGIC(snd_pcm_hw_params_get_buffer_size);                                 \
+    MAGIC(snd_pcm_hw_params_get_buffer_time_min);                             \
+    MAGIC(snd_pcm_hw_params_get_buffer_time_max);                             \
+    MAGIC(snd_pcm_hw_params_get_channels);                                    \
+    MAGIC(snd_pcm_hw_params_get_period_size);                                 \
+    MAGIC(snd_pcm_hw_params_get_period_time_max);                             \
+    MAGIC(snd_pcm_hw_params_get_period_time_min);                             \
+    MAGIC(snd_pcm_hw_params_get_periods);                                     \
     MAGIC(snd_pcm_hw_params_set_access);                                      \
-    MAGIC(snd_pcm_hw_params_set_format);                                      \
+    MAGIC(snd_pcm_hw_params_set_buffer_size_min);                             \
+    MAGIC(snd_pcm_hw_params_set_buffer_size_near);                            \
+    MAGIC(snd_pcm_hw_params_set_buffer_time_near);                            \
     MAGIC(snd_pcm_hw_params_set_channels);                                    \
+    MAGIC(snd_pcm_hw_params_set_channels_near);                               \
+    MAGIC(snd_pcm_hw_params_set_format);                                      \
+    MAGIC(snd_pcm_hw_params_set_period_time_near);                            \
+    MAGIC(snd_pcm_hw_params_set_period_size_near);                            \
     MAGIC(snd_pcm_hw_params_set_periods_near);                                \
     MAGIC(snd_pcm_hw_params_set_rate_near);                                   \
     MAGIC(snd_pcm_hw_params_set_rate);                                        \
     MAGIC(snd_pcm_hw_params_set_rate_resample);                               \
-    MAGIC(snd_pcm_hw_params_set_buffer_time_near);                            \
-    MAGIC(snd_pcm_hw_params_set_period_time_near);                            \
-    MAGIC(snd_pcm_hw_params_set_buffer_size_near);                            \
-    MAGIC(snd_pcm_hw_params_set_period_size_near);                            \
-    MAGIC(snd_pcm_hw_params_set_buffer_size_min);                             \
-    MAGIC(snd_pcm_hw_params_get_buffer_time_min);                             \
-    MAGIC(snd_pcm_hw_params_get_buffer_time_max);                             \
-    MAGIC(snd_pcm_hw_params_get_period_time_min);                             \
-    MAGIC(snd_pcm_hw_params_get_period_time_max);                             \
-    MAGIC(snd_pcm_hw_params_get_buffer_size);                                 \
-    MAGIC(snd_pcm_hw_params_get_period_size);                                 \
-    MAGIC(snd_pcm_hw_params_get_access);                                      \
-    MAGIC(snd_pcm_hw_params_get_periods);                                     \
     MAGIC(snd_pcm_hw_params_test_format);                                     \
     MAGIC(snd_pcm_hw_params_test_channels);                                   \
     MAGIC(snd_pcm_hw_params);                                                 \
-    MAGIC(snd_pcm_sw_params_malloc);                                          \
+    MAGIC(snd_pcm_sw_params);                                                 \
     MAGIC(snd_pcm_sw_params_current);                                         \
+    MAGIC(snd_pcm_sw_params_free);                                            \
+    MAGIC(snd_pcm_sw_params_malloc);                                          \
     MAGIC(snd_pcm_sw_params_set_avail_min);                                   \
     MAGIC(snd_pcm_sw_params_set_stop_threshold);                              \
-    MAGIC(snd_pcm_sw_params);                                                 \
-    MAGIC(snd_pcm_sw_params_free);                                            \
     MAGIC(snd_pcm_prepare);                                                   \
     MAGIC(snd_pcm_start);                                                     \
     MAGIC(snd_pcm_resume);                                                    \
@@ -105,7 +107,6 @@ constexpr char alsaDevice[] = "ALSA Default";
     MAGIC(snd_pcm_delay);                                                     \
     MAGIC(snd_pcm_state);                                                     \
     MAGIC(snd_pcm_avail_update);                                              \
-    MAGIC(snd_pcm_areas_silence);                                             \
     MAGIC(snd_pcm_mmap_begin);                                                \
     MAGIC(snd_pcm_mmap_commit);                                               \
     MAGIC(snd_pcm_readi);                                                     \
@@ -150,6 +151,7 @@ ALSA_FUNCS(MAKE_FUNC);
 #define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access
 #define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format
 #define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels
+#define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near
 #define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near
 #define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near
 #define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate
@@ -167,6 +169,7 @@ ALSA_FUNCS(MAKE_FUNC);
 #define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size
 #define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access
 #define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods
+#define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels
 #define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format
 #define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels
 #define snd_pcm_hw_params psnd_pcm_hw_params
@@ -184,7 +187,6 @@ ALSA_FUNCS(MAKE_FUNC);
 #define snd_pcm_delay psnd_pcm_delay
 #define snd_pcm_state psnd_pcm_state
 #define snd_pcm_avail_update psnd_pcm_avail_update
-#define snd_pcm_areas_silence psnd_pcm_areas_silence
 #define snd_pcm_mmap_begin psnd_pcm_mmap_begin
 #define snd_pcm_mmap_commit psnd_pcm_mmap_commit
 #define snd_pcm_readi psnd_pcm_readi
@@ -260,29 +262,36 @@ al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
     snd_pcm_info_t *pcminfo;
     snd_pcm_info_malloc(&pcminfo);
 
-    devlist.emplace_back(DevMap{alsaDevice,
-        GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture",
-            "default")});
+    auto defname = ConfigValueStr(nullptr, "alsa",
+        (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture");
+    devlist.emplace_back(DevMap{alsaDevice, defname ? defname->c_str() : "default"});
 
-    const char *customdevs{GetConfigValue(nullptr, "alsa",
-        (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")};
-    while(const char *curdev{customdevs})
+    if(auto customdevs = ConfigValueStr(nullptr, "alsa",
+        (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures"))
     {
-        if(!curdev[0]) break;
-        customdevs = strchr(curdev, ';');
-        const char *sep{strchr(curdev, '=')};
-        if(!sep)
+        size_t nextpos{customdevs->find_first_not_of(';')};
+        size_t curpos;
+        while((curpos=nextpos) < customdevs->length())
         {
-            std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)};
-            ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
-            continue;
-        }
+            nextpos = customdevs->find_first_of(';', curpos+1);
 
-        const char *oldsep{sep++};
-        devlist.emplace_back(DevMap{std::string(curdev, oldsep),
-            customdevs ? std::string(sep, customdevs++) : std::string(sep)});
-        const auto &entry = devlist.back();
-        TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
+            size_t seppos{customdevs->find_first_of('=', curpos)};
+            if(seppos == curpos || seppos >= nextpos)
+            {
+                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();
+                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);
+        }
     }
 
     const std::string main_prefix{
@@ -407,7 +416,7 @@ int verify_state(snd_pcm_t *handle)
 
 
 struct AlsaPlayback final : public BackendBase {
-    AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~AlsaPlayback() override;
 
     int mixerProc();
@@ -424,6 +433,7 @@ struct AlsaPlayback final : public BackendBase {
 
     std::mutex mMutex;
 
+    uint mFrameStep{};
     al::vector<al::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
@@ -445,7 +455,6 @@ int AlsaPlayback::mixerProc()
     SetRTPriority();
     althrd_setname(MIXER_THREAD_NAME);
 
-    const size_t samplebits{mDevice->bytesFromFmt() * 8};
     const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
     const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
     while(!mKillNow.load(std::memory_order_acquire))
@@ -507,7 +516,7 @@ int AlsaPlayback::mixerProc()
             }
 
             char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
-            mDevice->renderSamples(WritePtr, static_cast<uint>(frames), areas->step/samplebits);
+            mDevice->renderSamples(WritePtr, static_cast<uint>(frames), mFrameStep);
 
             snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
             if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
@@ -529,7 +538,6 @@ int AlsaPlayback::mixerNoMMapProc()
     SetRTPriority();
     althrd_setname(MIXER_THREAD_NAME);
 
-    const size_t frame_step{mDevice->channelsFromFmt()};
     const snd_pcm_uframes_t update_size{mDevice->UpdateSize};
     const snd_pcm_uframes_t buffer_size{mDevice->BufferSize};
     while(!mKillNow.load(std::memory_order_acquire))
@@ -575,7 +583,7 @@ int AlsaPlayback::mixerNoMMapProc()
         al::byte *WritePtr{mBuffer.data()};
         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), frame_step);
+        mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
         while(avail > 0)
         {
             snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
@@ -615,16 +623,15 @@ int AlsaPlayback::mixerNoMMapProc()
 
 void AlsaPlayback::open(const char *name)
 {
-    const char *driver{};
+    al::optional<std::string> driveropt;
+    const char *driver{"default"};
     if(name)
     {
         if(PlaybackDevices.empty())
             PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
 
         auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
-            [name](const DevMap &entry) -> bool
-            { return entry.name == name; }
-        );
+            [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};
@@ -633,14 +640,19 @@ void AlsaPlayback::open(const char *name)
     else
     {
         name = alsaDevice;
-        driver = GetConfigValue(nullptr, "alsa", "device", "default");
+        if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")})
+            driver = driveropt->c_str();
     }
-
     TRACE("Opening device \"%s\"\n", driver);
-    int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)};
+
+    snd_pcm_t *pcmHandle{};
+    int err{snd_pcm_open(&pcmHandle, driver, 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};
+    if(mPcmHandle)
+        snd_pcm_close(mPcmHandle);
+    mPcmHandle = pcmHandle;
 
     /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
     snd_config_update_free_global();
@@ -723,37 +735,25 @@ bool AlsaPlayback::reset()
         }
     }
     CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
-    /* test and set channels (implicitly sets frame bits) */
-    if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
+    /* set channels (implicitly sets frame bits) */
+    if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0)
     {
-        static const DevFmtChannels channellist[] = {
-            DevFmtStereo,
-            DevFmtQuad,
-            DevFmtX51,
-            DevFmtX71,
-            DevFmtMono,
-        };
-
-        for(const auto &chan : channellist)
-        {
-            if(snd_pcm_hw_params_test_channels(mPcmHandle, hp.get(), ChannelsFromDevFmt(chan, 0)) >= 0)
-            {
-                mDevice->FmtChans = chan;
-                mDevice->mAmbiOrder = 0;
-                break;
-            }
-        }
+        uint numchans{2u};
+        CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans));
+        if(numchans < 1)
+            throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"};
+        if(numchans == 1) mDevice->FmtChans = DevFmtMono;
+        else mDevice->FmtChans = DevFmtStereo;
     }
-    CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
     /* set rate (implicitly constrains period/buffer parameters) */
     if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0)
         || !mDevice->Flags.test(FrequencyRequest))
     {
         if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0)
-            ERR("Failed to disable ALSA resampler\n");
+            WARN("Failed to disable ALSA resampler\n");
     }
     else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
-        ERR("Failed to enable ALSA resampler\n");
+        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)
@@ -772,6 +772,7 @@ bool AlsaPlayback::reset()
     CHECK(snd_pcm_hw_params_get_access(hp.get(), &access));
     CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr));
     CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames));
+    CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep));
     hp = nullptr;
 
     SwParamsPtr sp{CreateSwParams()};
@@ -809,8 +810,8 @@ void AlsaPlayback::start()
     int (AlsaPlayback::*thread_func)(){};
     if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
     {
-        mBuffer.resize(
-            static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize)));
+        auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->UpdateSize);
+        mBuffer.resize(static_cast<size_t>(datalen));
         thread_func = &AlsaPlayback::mixerNoMMapProc;
     }
     else
@@ -863,7 +864,7 @@ ClockLatency AlsaPlayback::getClockLatency()
 
 
 struct AlsaCapture final : public BackendBase {
-    AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~AlsaCapture() override;
 
     void open(const char *name) override;
@@ -895,16 +896,15 @@ AlsaCapture::~AlsaCapture()
 
 void AlsaCapture::open(const char *name)
 {
-    const char *driver{};
+    al::optional<std::string> driveropt;
+    const char *driver{"default"};
     if(name)
     {
         if(CaptureDevices.empty())
             CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
 
         auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
-            [name](const DevMap &entry) -> bool
-            { return entry.name == name; }
-        );
+            [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};
@@ -913,7 +913,8 @@ void AlsaCapture::open(const char *name)
     else
     {
         name = alsaDevice;
-        driver = GetConfigValue(nullptr, "alsa", "capture", "default");
+        if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")})
+            driver = driveropt->c_str();
     }
 
     TRACE("Opening device \"%s\"\n", driver);
@@ -1252,7 +1253,7 @@ std::string AlsaBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new AlsaPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/alsa.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_ALSA_H
 #define BACKENDS_ALSA_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct AlsaBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 16 - 17
libs/openal-soft/Alc/backends/base.cpp

@@ -3,21 +3,22 @@
 
 #include "base.h"
 
+#include <algorithm>
+#include <array>
 #include <atomic>
-#include <thread>
 
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 #include <mmreg.h>
-#endif
 
 #include "albit.h"
-#include "alcmain.h"
-#include "alnumeric.h"
+#include "core/logging.h"
 #include "aloptional.h"
+#endif
+
 #include "atomic.h"
-#include "core/logging.h"
+#include "core/devformat.h"
 
 
 bool BackendBase::reset()
@@ -78,14 +79,6 @@ void BackendBase::setDefaultWFXChannelOrder()
         mDevice->RealOut.ChannelIndex[SideLeft]    = 4;
         mDevice->RealOut.ChannelIndex[SideRight]   = 5;
         break;
-    case DevFmtX51Rear:
-        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;
-        break;
     case DevFmtX61:
         mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
         mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
@@ -116,11 +109,11 @@ void BackendBase::setDefaultChannelOrder()
 
     switch(mDevice->FmtChans)
     {
-    case DevFmtX51Rear:
+    case DevFmtX51:
         mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
         mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
-        mDevice->RealOut.ChannelIndex[BackLeft]    = 2;
-        mDevice->RealOut.ChannelIndex[BackRight]   = 3;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 2;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 3;
         mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
         mDevice->RealOut.ChannelIndex[LFE]         = 5;
         return;
@@ -139,7 +132,6 @@ void BackendBase::setDefaultChannelOrder()
     case DevFmtMono:
     case DevFmtStereo:
     case DevFmtQuad:
-    case DevFmtX51:
     case DevFmtX61:
     case DevFmtAmbi3D:
         setDefaultWFXChannelOrder();
@@ -150,6 +142,13 @@ void BackendBase::setDefaultChannelOrder()
 #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)

+ 12 - 8
libs/openal-soft/Alc/backends/base.h

@@ -2,12 +2,13 @@
 #define ALC_BACKENDS_BASE_H
 
 #include <chrono>
+#include <cstdarg>
 #include <memory>
-#include <mutex>
+#include <ratio>
 #include <string>
 
 #include "albyte.h"
-#include "alcmain.h"
+#include "core/device.h"
 #include "core/except.h"
 
 
@@ -30,9 +31,9 @@ struct BackendBase {
 
     virtual ClockLatency getClockLatency();
 
-    ALCdevice *const mDevice;
+    DeviceBase *const mDevice;
 
-    BackendBase(ALCdevice *device) noexcept : mDevice{device} { }
+    BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
     virtual ~BackendBase() = default;
 
 protected:
@@ -57,7 +58,7 @@ 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(ALCdevice *device)
+inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
 {
     using std::chrono::seconds;
     using std::chrono::nanoseconds;
@@ -69,9 +70,8 @@ inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
 /* Helper to get the device latency from the backend, including any fixed
  * latency from post-processing.
  */
-inline ClockLatency GetClockLatency(ALCdevice *device)
+inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
 {
-    BackendBase *backend{device->Backend.get()};
     ClockLatency ret{backend->getClockLatency()};
     ret.Latency += device->FixedLatency;
     return ret;
@@ -85,7 +85,7 @@ struct BackendFactory {
 
     virtual std::string probe(BackendType type) = 0;
 
-    virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
+    virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
 
 protected:
     virtual ~BackendFactory() = default;
@@ -103,7 +103,11 @@ class backend_exception final : public base_exception {
     backend_error mErrorCode;
 
 public:
+#ifdef __USE_MINGW_ANSI_STDIO
+    [[gnu::format(gnu_printf, 3, 4)]]
+#else
     [[gnu::format(printf, 3, 4)]]
+#endif
     backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
     {
         std::va_list args;

+ 378 - 120
libs/openal-soft/Alc/backends/coreaudio.cpp

@@ -20,34 +20,239 @@
 
 #include "config.h"
 
-#include "backends/coreaudio.h"
+#include "coreaudio.h"
 
 #include <inttypes.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <cmath>
+#include <memory>
+#include <string>
 
-#include "alcmain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "converter.h"
+#include "alnumeric.h"
+#include "core/converter.h"
+#include "core/device.h"
 #include "core/logging.h"
-#include "backends/base.h"
+#include "ringbuffer.h"
 
-#include <unistd.h>
 #include <AudioUnit/AudioUnit.h>
 #include <AudioToolbox/AudioToolbox.h>
 
 
 namespace {
 
-static const char ca_device[] = "CoreAudio Default";
+#if TARGET_OS_IOS || TARGET_OS_TV
+#define CAN_ENUMERATE 0
+#else
+#define CAN_ENUMERATE 1
+#endif
+
+
+#if CAN_ENUMERATE
+struct DeviceEntry {
+    AudioDeviceID mId;
+    std::string mName;
+};
+
+std::vector<DeviceEntry> PlaybackList;
+std::vector<DeviceEntry> CaptureList;
+
+
+OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
+{
+    const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+        kAudioObjectPropertyElementMaster};
+    return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
+        propData);
+}
+
+OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
+{
+    const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
+        kAudioObjectPropertyElementMaster};
+    return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
+}
+
+OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
+    UInt32 elem, UInt32 dataSize, void *propData)
+{
+    static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+        kAudioDevicePropertyScopeInput};
+    const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
+    return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
+}
+
+OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
+    bool isCapture, UInt32 elem, UInt32 *outSize)
+{
+    static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
+        kAudioDevicePropertyScopeInput};
+    const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
+    return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
+}
+
+
+std::string GetDeviceName(AudioDeviceID devId)
+{
+    std::string devname;
+    CFStringRef nameRef;
+
+    /* Try to get the device name as a CFString, for Unicode name support. */
+    OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
+        sizeof(nameRef), &nameRef)};
+    if(err == noErr)
+    {
+        const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
+            kCFStringEncodingUTF8)};
+        devname.resize(static_cast<size_t>(propSize)+1, '\0');
+
+        CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
+        CFRelease(nameRef);
+    }
+    else
+    {
+        /* If that failed, just get the C string. Hopefully there's nothing bad
+         * with this.
+         */
+        UInt32 propSize{};
+        if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
+            return devname;
+
+        devname.resize(propSize+1, '\0');
+        if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
+        {
+            devname.clear();
+            return devname;
+        }
+    }
+
+    /* Clear extraneous nul chars that may have been written with the name
+     * string, and return it.
+     */
+    while(!devname.back())
+        devname.pop_back();
+    return devname;
+}
+
+UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
+{
+    UInt32 propSize{};
+    auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
+        &propSize);
+    if(err)
+    {
+        ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
+        return 0;
+    }
+
+    auto buflist_data = std::make_unique<char[]>(propSize);
+    auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
+
+    err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
+        buflist);
+    if(err)
+    {
+        ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
+        return 0;
+    }
+
+    UInt32 numChannels{0};
+    for(size_t i{0};i < buflist->mNumberBuffers;++i)
+        numChannels += buflist->mBuffers[i].mNumberChannels;
+
+    return numChannels;
+}
+
+
+void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
+{
+    UInt32 propSize{};
+    if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
+    {
+        ERR("Failed to get device list size: %u\n", err);
+        return;
+    }
+
+    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);
+        return;
+    }
+
+    std::vector<DeviceEntry> newdevs;
+    newdevs.reserve(devIds.size());
+
+    AudioDeviceID defaultId{kAudioDeviceUnknown};
+    GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
+        kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
+
+    if(defaultId != kAudioDeviceUnknown)
+    {
+        newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
+        const auto &entry = newdevs.back();
+        TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+    }
+    for(const AudioDeviceID devId : devIds)
+    {
+        if(devId == kAudioDeviceUnknown)
+            continue;
+
+        auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
+        { return entry.mId == devId; };
+        auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
+        if(match != newdevs.cend()) continue;
+
+        auto numChannels = GetDeviceChannelCount(devId, isCapture);
+        if(numChannels > 0)
+        {
+            newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
+            const auto &entry = newdevs.back();
+            TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
+        }
+    }
+
+    if(newdevs.size() > 1)
+    {
+        /* Rename entries that have matching names, by appending '#2', '#3',
+         * etc, as needed.
+         */
+        for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
+        {
+            auto check_match = [curitem](const DeviceEntry &entry) -> bool
+            { return entry.mName == curitem->mName; };
+            if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
+            {
+                std::string name{curitem->mName};
+                size_t count{1};
+                auto check_name = [&name](const DeviceEntry &entry) -> bool
+                { return entry.mName == name; };
+                do {
+                    name = curitem->mName;
+                    name += " #";
+                    name += std::to_string(++count);
+                } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
+                curitem->mName = std::move(name);
+            }
+        }
+    }
+
+    newdevs.shrink_to_fit();
+    newdevs.swap(list);
+}
+
+#else
+
+static constexpr char ca_device[] = "CoreAudio Default";
+#endif
 
 
 struct CoreAudioPlayback final : public BackendBase {
-    CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~CoreAudioPlayback() override;
 
     OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -96,19 +301,41 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi
 
 void CoreAudioPlayback::open(const char *name)
 {
+#if CAN_ENUMERATE
+    AudioDeviceID audioDevice{kAudioDeviceUnknown};
+    if(!name)
+        GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
+            &audioDevice);
+    else
+    {
+        if(PlaybackList.empty())
+            EnumerateDevices(PlaybackList, false);
+
+        auto find_name = [name](const DeviceEntry &entry) -> bool
+        { return entry.mName == name; };
+        auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
+        if(devmatch == PlaybackList.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
+
+        audioDevice = devmatch->mId;
+    }
+#else
     if(!name)
         name = ca_device;
     else if(strcmp(name, ca_device) != 0)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
+#endif
 
     /* open the default output unit */
     AudioComponentDescription desc{};
     desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
-    desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+    desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+        kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
 #else
-    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+    desc.componentSubType = kAudioUnitSubType_RemoteIO;
 #endif
     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
     desc.componentFlags = 0;
@@ -118,18 +345,50 @@ void CoreAudioPlayback::open(const char *name)
     if(comp == nullptr)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
 
-    OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
+    AudioUnit audioUnit{};
+    OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
     if(err != noErr)
         throw al::backend_exception{al::backend_error::NoDevice,
             "Could not create component instance: %u", err};
 
-    /* init and start the default audio unit... */
-    err = AudioUnitInitialize(mAudioUnit);
+#if CAN_ENUMERATE
+    if(audioDevice != kAudioDeviceUnknown)
+        AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+            kAudioUnitScope_Global, 0, &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};
 
+    /* WARNING: I don't know if "valid" audio unit values are guaranteed to be
+     * non-0. If not, this logic is broken.
+     */
+    if(mAudioUnit)
+    {
+        AudioUnitUninitialize(mAudioUnit);
+        AudioComponentInstanceDispose(mAudioUnit);
+    }
+    mAudioUnit = audioUnit;
+
+#if CAN_ENUMERATE
+    if(name)
+        mDevice->DeviceName = name;
+    else
+    {
+        UInt32 propSize{sizeof(audioDevice)};
+        audioDevice = kAudioDeviceUnknown;
+        AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
+            kAudioUnitScope_Global, 0, &audioDevice, &propSize);
+
+        std::string devname{GetDeviceName(audioDevice)};
+        if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+        else mDevice->DeviceName = "Unknown Device Name";
+    }
+#else
     mDevice->DeviceName = name;
+#endif
 }
 
 bool CoreAudioPlayback::reset()
@@ -140,10 +399,10 @@ bool CoreAudioPlayback::reset()
 
     /* retrieve default output unit's properties (output side) */
     AudioStreamBasicDescription streamFormat{};
-    auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
+    UInt32 size{sizeof(streamFormat)};
     err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
         0, &streamFormat, &size);
-    if(err != noErr || size != sizeof(AudioStreamBasicDescription))
+    if(err != noErr || size != sizeof(streamFormat))
     {
         ERR("AudioUnitGetProperty failed\n");
         return false;
@@ -159,15 +418,9 @@ bool CoreAudioPlayback::reset()
     TRACE("  streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
 #endif
 
-    /* set default output unit's input side to match output side */
-    err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
-        0, &streamFormat, size);
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        return false;
-    }
-
+    /* Use the sample rate from the output unit's current parameters, but reset
+     * everything else.
+     */
     if(mDevice->Frequency != streamFormat.mSampleRate)
     {
         mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
@@ -176,82 +429,54 @@ bool CoreAudioPlayback::reset()
     }
 
     /* FIXME: How to tell what channels are what in the output device, and how
-     * to specify what we're giving?  eg, 6.0 vs 5.1 */
-    switch(streamFormat.mChannelsPerFrame)
-    {
-        case 1:
-            mDevice->FmtChans = DevFmtMono;
-            break;
-        case 2:
-            mDevice->FmtChans = DevFmtStereo;
-            break;
-        case 4:
-            mDevice->FmtChans = DevFmtQuad;
-            break;
-        case 6:
-            mDevice->FmtChans = DevFmtX51;
-            break;
-        case 7:
-            mDevice->FmtChans = DevFmtX61;
-            break;
-        case 8:
-            mDevice->FmtChans = DevFmtX71;
-            break;
-        default:
-            ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
-            mDevice->FmtChans = DevFmtStereo;
-            streamFormat.mChannelsPerFrame = 2;
-            break;
-    }
-    setDefaultWFXChannelOrder();
+     * to specify what we're giving? e.g. 6.0 vs 5.1
+     */
+    streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
 
-    /* use channel count and sample rate from the default output unit's current
-     * parameters, but reset everything else */
     streamFormat.mFramesPerPacket = 1;
-    streamFormat.mFormatFlags = 0;
+    streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
+    streamFormat.mFormatID = kAudioFormatLinearPCM;
     switch(mDevice->FmtType)
     {
         case DevFmtUByte:
             mDevice->FmtType = DevFmtByte;
             /* fall-through */
         case DevFmtByte:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
             streamFormat.mBitsPerChannel = 8;
             break;
         case DevFmtUShort:
             mDevice->FmtType = DevFmtShort;
             /* fall-through */
         case DevFmtShort:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
             streamFormat.mBitsPerChannel = 16;
             break;
         case DevFmtUInt:
             mDevice->FmtType = DevFmtInt;
             /* fall-through */
         case DevFmtInt:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
             streamFormat.mBitsPerChannel = 32;
             break;
         case DevFmtFloat:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
+            streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
             streamFormat.mBitsPerChannel = 32;
             break;
     }
-    streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
-                                  streamFormat.mBitsPerChannel / 8;
-    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
-    streamFormat.mFormatID = kAudioFormatLinearPCM;
-    streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
-                                 kLinearPCMFormatFlagIsPacked;
+    streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8;
+    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket;
 
     err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
-        0, &streamFormat, sizeof(AudioStreamBasicDescription));
+        0, &streamFormat, sizeof(streamFormat));
     if(err != noErr)
     {
         ERR("AudioUnitSetProperty failed\n");
         return false;
     }
 
+    setDefaultWFXChannelOrder();
+
     /* setup callback */
     mFrameSize = mDevice->frameSizeFromFmt();
     AURenderCallbackStruct input{};
@@ -294,7 +519,7 @@ void CoreAudioPlayback::stop()
 
 
 struct CoreAudioCapture final : public BackendBase {
-    CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~CoreAudioCapture() override;
 
     OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
@@ -383,45 +608,64 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
 
 void CoreAudioCapture::open(const char *name)
 {
-    AudioStreamBasicDescription requestedFormat;  // The application requested format
-    AudioStreamBasicDescription hardwareFormat;   // The hardware format
-    AudioStreamBasicDescription outputFormat;     // The AudioUnit output format
-    AURenderCallbackStruct input;
-    AudioComponentDescription desc;
-    UInt32 propertySize;
-    UInt32 enableIO;
-    AudioComponent comp;
-    OSStatus err;
+#if CAN_ENUMERATE
+    AudioDeviceID audioDevice{kAudioDeviceUnknown};
+    if(!name)
+        GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
+            &audioDevice);
+    else
+    {
+        if(CaptureList.empty())
+            EnumerateDevices(CaptureList, true);
+
+        auto find_name = [name](const DeviceEntry &entry) -> bool
+        { return entry.mName == name; };
+        auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
+        if(devmatch == CaptureList.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
 
+        audioDevice = devmatch->mId;
+    }
+#else
     if(!name)
         name = ca_device;
     else if(strcmp(name, ca_device) != 0)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
+#endif
 
+    AudioComponentDescription desc{};
     desc.componentType = kAudioUnitType_Output;
-#if TARGET_OS_IOS
-    desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#if CAN_ENUMERATE
+    desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
+        kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
 #else
-    desc.componentSubType = kAudioUnitSubType_HALOutput;
+    desc.componentSubType = kAudioUnitSubType_RemoteIO;
 #endif
     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
     desc.componentFlags = 0;
     desc.componentFlagsMask = 0;
 
     // Search for component with given description
-    comp = AudioComponentFindNext(NULL, &desc);
+    AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
     if(comp == NULL)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
 
     // Open the component
-    err = AudioComponentInstanceNew(comp, &mAudioUnit);
+    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
+
     // Turn off AudioUnit output
-    enableIO = 0;
+    UInt32 enableIO{0};
     err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
         kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
     if(err != noErr)
@@ -436,35 +680,8 @@ void CoreAudioCapture::open(const char *name)
         throw al::backend_exception{al::backend_error::DeviceError,
             "Could not enable audio unit input property: %u", err};
 
-#if !TARGET_OS_IOS
-    {
-        // Get the default input device
-        AudioDeviceID inputDevice = kAudioDeviceUnknown;
-
-        propertySize = sizeof(AudioDeviceID);
-        AudioObjectPropertyAddress propertyAddress{};
-        propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
-        propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
-        propertyAddress.mElement = kAudioObjectPropertyElementMaster;
-
-        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
-            &propertySize, &inputDevice);
-        if(err != noErr)
-            throw al::backend_exception{al::backend_error::NoDevice,
-                "Could not get input device: %u", err};
-        if(inputDevice == kAudioDeviceUnknown)
-            throw al::backend_exception{al::backend_error::NoDevice, "Unknown input device"};
-
-        // Track the input device
-        err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
-            kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
-        if(err != noErr)
-            throw al::backend_exception{al::backend_error::NoDevice,
-                "Could not set input device: %u", err};
-    }
-#endif
-
     // set capture callback
+    AURenderCallbackStruct input{};
     input.inputProc = CoreAudioCapture::RecordProcC;
     input.inputProcRefCon = this;
 
@@ -489,14 +706,16 @@ void CoreAudioCapture::open(const char *name)
             "Could not initialize audio unit: %u", err};
 
     // Get the hardware format
-    propertySize = sizeof(AudioStreamBasicDescription);
+    AudioStreamBasicDescription hardwareFormat{};
+    UInt32 propertySize{sizeof(hardwareFormat)};
     err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
         1, &hardwareFormat, &propertySize);
-    if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
+    if(err != noErr || propertySize != sizeof(hardwareFormat))
         throw al::backend_exception{al::backend_error::DeviceError,
             "Could not get input format: %u", err};
 
     // Set up the requested format description
+    AudioStreamBasicDescription requestedFormat{};
     switch(mDevice->FmtType)
     {
     case DevFmtByte:
@@ -509,7 +728,8 @@ void CoreAudioCapture::open(const char *name)
         break;
     case DevFmtShort:
         requestedFormat.mBitsPerChannel = 16;
-        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+            | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
         break;
     case DevFmtUShort:
         requestedFormat.mBitsPerChannel = 16;
@@ -517,7 +737,8 @@ void CoreAudioCapture::open(const char *name)
         break;
     case DevFmtInt:
         requestedFormat.mBitsPerChannel = 32;
-        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
+            | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
         break;
     case DevFmtUInt:
         requestedFormat.mBitsPerChannel = 32;
@@ -525,7 +746,8 @@ void CoreAudioCapture::open(const char *name)
         break;
     case DevFmtFloat:
         requestedFormat.mBitsPerChannel = 32;
-        requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
+            | kAudioFormatFlagIsPacked;
         break;
     }
 
@@ -540,7 +762,6 @@ void CoreAudioCapture::open(const char *name)
 
     case DevFmtQuad:
     case DevFmtX51:
-    case DevFmtX51Rear:
     case DevFmtX61:
     case DevFmtX71:
     case DevFmtAmbi3D:
@@ -561,7 +782,7 @@ void CoreAudioCapture::open(const char *name)
 
     // Use intermediate format for sample rate conversion (outputFormat)
     // Set sample rate to the same as hardware for resampling later
-    outputFormat = requestedFormat;
+    AudioStreamBasicDescription outputFormat{requestedFormat};
     outputFormat.mSampleRate = hardwareFormat.mSampleRate;
 
     // The output format should be the requested format, but using the hardware sample rate
@@ -600,7 +821,23 @@ void CoreAudioCapture::open(const char *name)
             mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
             mDevice->Frequency, Resampler::FastBSinc24);
 
+#if CAN_ENUMERATE
+    if(name)
+        mDevice->DeviceName = name;
+    else
+    {
+        UInt32 propSize{sizeof(audioDevice)};
+        audioDevice = kAudioDeviceUnknown;
+        AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
+            kAudioUnitScope_Global, 0, &audioDevice, &propSize);
+
+        std::string devname{GetDeviceName(audioDevice)};
+        if(!devname.empty()) mDevice->DeviceName = std::move(devname);
+        else mDevice->DeviceName = "Unknown Device Name";
+    }
+#else
     mDevice->DeviceName = name;
+#endif
 }
 
 
@@ -665,6 +902,26 @@ bool CoreAudioBackendFactory::querySupport(BackendType type)
 std::string CoreAudioBackendFactory::probe(BackendType type)
 {
     std::string outnames;
+#if CAN_ENUMERATE
+    auto append_name = [&outnames](const DeviceEntry &entry) -> void
+    {
+        /* Includes null char. */
+        outnames.append(entry.mName.c_str(), entry.mName.length()+1);
+    };
+    switch(type)
+    {
+    case BackendType::Playback:
+        EnumerateDevices(PlaybackList, false);
+        std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+        break;
+    case BackendType::Capture:
+        EnumerateDevices(CaptureList, true);
+        std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
+        break;
+    }
+
+#else
+
     switch(type)
     {
     case BackendType::Playback:
@@ -673,10 +930,11 @@ std::string CoreAudioBackendFactory::probe(BackendType type)
         outnames.append(ca_device, sizeof(ca_device));
         break;
     }
+#endif
     return outnames;
 }
 
-BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new CoreAudioPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/coreaudio.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_COREAUDIO_H
 #define BACKENDS_COREAUDIO_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct CoreAudioBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 39 - 64
libs/openal-soft/Alc/backends/dsound.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/dsound.h"
+#include "dsound.h"
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
@@ -44,9 +44,10 @@
 #include <algorithm>
 #include <functional>
 
-#include "alcmain.h"
-#include "alu.h"
-#include "compat.h"
+#include "alnumeric.h"
+#include "comptr.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "dynload.h"
 #include "ringbuffer.h"
@@ -169,21 +170,21 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi
 
 
 struct DSoundPlayback final : public BackendBase {
-    DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~DSoundPlayback() override;
 
     int mixerProc();
 
-    void open(const ALCchar *name) override;
+    void open(const char *name) override;
     bool reset() override;
     void start() override;
     void stop() override;
 
-    IDirectSound       *mDS{nullptr};
-    IDirectSoundBuffer *mPrimaryBuffer{nullptr};
-    IDirectSoundBuffer *mBuffer{nullptr};
-    IDirectSoundNotify *mNotifies{nullptr};
-    HANDLE             mNotifyEvent{nullptr};
+    ComPtr<IDirectSound>       mDS;
+    ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
+    ComPtr<IDirectSoundBuffer> mBuffer;
+    ComPtr<IDirectSoundNotify> mNotifies;
+    HANDLE mNotifyEvent{nullptr};
 
     std::atomic<bool> mKillNow{true};
     std::thread mThread;
@@ -193,19 +194,11 @@ struct DSoundPlayback final : public BackendBase {
 
 DSoundPlayback::~DSoundPlayback()
 {
-    if(mNotifies)
-        mNotifies->Release();
     mNotifies = nullptr;
-    if(mBuffer)
-        mBuffer->Release();
     mBuffer = nullptr;
-    if(mPrimaryBuffer)
-        mPrimaryBuffer->Release();
     mPrimaryBuffer = nullptr;
-
-    if(mDS)
-        mDS->Release();
     mDS = nullptr;
+
     if(mNotifyEvent)
         CloseHandle(mNotifyEvent);
     mNotifyEvent = nullptr;
@@ -234,8 +227,8 @@ FORCE_ALIGN int DSoundPlayback::mixerProc()
     bool Playing{false};
     DWORD LastCursor{0u};
     mBuffer->GetCurrentPosition(&LastCursor, nullptr);
-    while(!mKillNow.load(std::memory_order_acquire) &&
-          mDevice->Connected.load(std::memory_order_acquire))
+    while(!mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
     {
         // Get current play cursor
         DWORD PlayCursor;
@@ -344,31 +337,34 @@ void DSoundPlayback::open(const char *name)
     }
 
     hr = DS_OK;
-    mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
-    if(!mNotifyEvent) hr = E_FAIL;
+    if(!mNotifyEvent)
+    {
+        mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+        if(!mNotifyEvent) hr = E_FAIL;
+    }
 
     //DirectSound Init code
+    ComPtr<IDirectSound> ds;
     if(SUCCEEDED(hr))
-        hr = DirectSoundCreate(guid, &mDS, nullptr);
+        hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
     if(SUCCEEDED(hr))
-        hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
+        hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
     if(FAILED(hr))
         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
             hr};
 
+    mNotifies = nullptr;
+    mBuffer = nullptr;
+    mPrimaryBuffer = nullptr;
+    mDS = std::move(ds);
+
     mDevice->DeviceName = name;
 }
 
 bool DSoundPlayback::reset()
 {
-    if(mNotifies)
-        mNotifies->Release();
     mNotifies = nullptr;
-    if(mBuffer)
-        mBuffer->Release();
     mBuffer = nullptr;
-    if(mPrimaryBuffer)
-        mPrimaryBuffer->Release();
     mPrimaryBuffer = nullptr;
 
     switch(mDevice->FmtType)
@@ -406,17 +402,14 @@ bool DSoundPlayback::reset()
                 mDevice->FmtChans = DevFmtStereo;
             else if(speakers == DSSPEAKER_QUAD)
                 mDevice->FmtChans = DevFmtQuad;
-            else if(speakers == DSSPEAKER_5POINT1_SURROUND)
+            else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK)
                 mDevice->FmtChans = DevFmtX51;
-            else if(speakers == DSSPEAKER_5POINT1_BACK)
-                mDevice->FmtChans = DevFmtX51Rear;
             else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
                 mDevice->FmtChans = DevFmtX71;
             else
                 ERR("Unknown system speaker config: 0x%lx\n", speakers);
         }
-        mDevice->IsHeadphones = mDevice->FmtChans == DevFmtStereo
-            && speakers == DSSPEAKER_HEADPHONE;
+        mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
 
         switch(mDevice->FmtChans)
         {
@@ -426,7 +419,6 @@ bool DSoundPlayback::reset()
         case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
         case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
         case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
-        case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break;
         case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
         case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
         }
@@ -454,8 +446,6 @@ retry_open:
         else
             OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
 
-        if(mPrimaryBuffer)
-            mPrimaryBuffer->Release();
         mPrimaryBuffer = nullptr;
     }
     else
@@ -465,7 +455,7 @@ retry_open:
             DSBUFFERDESC DSBDescription{};
             DSBDescription.dwSize = sizeof(DSBDescription);
             DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
-            hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
+            hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
         }
         if(SUCCEEDED(hr))
             hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
@@ -485,7 +475,7 @@ retry_open:
         DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
         DSBDescription.lpwfxFormat = &OutputType.Format;
 
-        hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
+        hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr);
         if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
         {
             mDevice->FmtType = DevFmtShort;
@@ -499,7 +489,7 @@ retry_open:
         hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
         if(SUCCEEDED(hr))
         {
-            mNotifies = static_cast<IDirectSoundNotify*>(ptr);
+            mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
 
             uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
             assert(num_updates <= MAX_UPDATES);
@@ -517,14 +507,8 @@ retry_open:
 
     if(FAILED(hr))
     {
-        if(mNotifies)
-            mNotifies->Release();
         mNotifies = nullptr;
-        if(mBuffer)
-            mBuffer->Release();
         mBuffer = nullptr;
-        if(mPrimaryBuffer)
-            mPrimaryBuffer->Release();
         mPrimaryBuffer = nullptr;
         return false;
     }
@@ -558,7 +542,7 @@ void DSoundPlayback::stop()
 
 
 struct DSoundCapture final : public BackendBase {
-    DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~DSoundCapture() override;
 
     void open(const char *name) override;
@@ -567,8 +551,8 @@ struct DSoundCapture final : public BackendBase {
     void captureSamples(al::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
-    IDirectSoundCapture *mDSC{nullptr};
-    IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
+    ComPtr<IDirectSoundCapture> mDSC;
+    ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
     DWORD mBufferBytes{0u};
     DWORD mCursor{0u};
 
@@ -582,12 +566,8 @@ DSoundCapture::~DSoundCapture()
     if(mDSCbuffer)
     {
         mDSCbuffer->Stop();
-        mDSCbuffer->Release();
         mDSCbuffer = nullptr;
     }
-
-    if(mDSC)
-        mDSC->Release();
     mDSC = nullptr;
 }
 
@@ -653,7 +633,6 @@ void DSoundCapture::open(const char *name)
     case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
     case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
     case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
-    case DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break;
     case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
     case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
     case DevFmtAmbi3D:
@@ -693,20 +672,16 @@ void DSoundCapture::open(const char *name)
     DSCBDescription.lpwfxFormat = &InputType.Format;
 
     //DirectSoundCapture Init code
-    hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
+    hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr);
     if(SUCCEEDED(hr))
-        mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
+        mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
     if(SUCCEEDED(hr))
          mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
 
     if(FAILED(hr))
     {
         mRing = nullptr;
-        if(mDSCbuffer)
-            mDSCbuffer->Release();
         mDSCbuffer = nullptr;
-        if(mDSC)
-            mDSC->Release();
         mDSC = nullptr;
 
         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
@@ -858,7 +833,7 @@ std::string DSoundBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new DSoundPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/dsound.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_DSOUND_H
 #define BACKENDS_DSOUND_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct DSoundBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 263 - 125
libs/openal-soft/Alc/backends/jack.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/jack.h"
+#include "jack.h"
 
 #include <cstdlib>
 #include <cstdio>
@@ -31,9 +31,10 @@
 #include <thread>
 #include <functional>
 
-#include "alcmain.h"
-#include "alu.h"
-#include "alconfig.h"
+#include "alc/alconfig.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "dynload.h"
 #include "ringbuffer.h"
@@ -45,9 +46,6 @@
 
 namespace {
 
-constexpr char jackDevice[] = "JACK Default";
-
-
 #ifdef HAVE_DYNLOAD
 #define JACK_FUNCS(MAGIC)          \
     MAGIC(jack_client_open);       \
@@ -101,6 +99,8 @@ decltype(jack_error_callback) * pjack_error_callback;
 #endif
 
 
+constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
+
 jack_options_t ClientOptions = JackNullOption;
 
 bool jack_load()
@@ -152,6 +152,11 @@ bool jack_load()
 }
 
 
+struct JackDeleter {
+    void operator()(void *ptr) { jack_free(ptr); }
+};
+using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
+
 struct DeviceEntry {
     std::string mName;
     std::string mPattern;
@@ -160,53 +165,125 @@ struct DeviceEntry {
 al::vector<DeviceEntry> PlaybackList;
 
 
-void EnumerateDevices(al::vector<DeviceEntry> &list)
+void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
 {
-    al::vector<DeviceEntry>{}.swap(list);
+    std::remove_reference_t<decltype(list)>{}.swap(list);
+
+    if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
+    {
+        for(size_t i{0};ports[i];++i)
+        {
+            const char *sep{std::strchr(ports[i], ':')};
+            if(!sep || ports[i] == sep) continue;
 
-    list.emplace_back(DeviceEntry{jackDevice, ""});
+            const al::span<const char> portdev{ports[i], sep};
+            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;
+            };
+            if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
+                continue;
 
-    std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")};
-    size_t strpos{0};
-    while(strpos < customList.size())
+            std::string name{portdev.data(), portdev.size()};
+            list.emplace_back(DeviceEntry{name, name+":"});
+            const auto &entry = list.back();
+            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
+         * generic entry.
+         */
+        if(ports[0] && list.empty())
+        {
+            WARN("No device names found in available ports, adding a generic name.\n");
+            list.emplace_back(DeviceEntry{"JACK", ""});
+        }
+    }
+
+    if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
     {
-        size_t nextpos{customList.find(';', strpos)};
-        size_t seppos{customList.find('=', strpos)};
-        if(seppos >= nextpos || seppos == strpos)
+        for(size_t strpos{0};strpos < listopt->size();)
         {
-            const std::string entry{customList.substr(strpos, nextpos-strpos)};
-            ERR("Invalid device entry: \"%s\"\n", entry.c_str());
+            size_t nextpos{listopt->find(';', strpos)};
+            size_t seppos{listopt->find('=', strpos)};
+            if(seppos >= nextpos || seppos == strpos)
+            {
+                const std::string entry{listopt->substr(strpos, nextpos-strpos)};
+                ERR("Invalid device entry: \"%s\"\n", entry.c_str());
+                if(nextpos != std::string::npos) ++nextpos;
+                strpos = nextpos;
+                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)};
+
+            /* 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;
+            };
+            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());
+                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();
+                TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
+            }
+
             if(nextpos != std::string::npos) ++nextpos;
             strpos = nextpos;
-            continue;
         }
+    }
 
-        size_t count{1};
-        std::string name{customList.substr(strpos, seppos-strpos)};
-        auto check_name = [&name](const DeviceEntry &entry) -> bool
-        { return entry.mName == name; };
-        while(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
+    if(list.size() > 1)
+    {
+        /* Rename entries that have matching names, by appending '#2', '#3',
+         * etc, as needed.
+         */
+        for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
         {
-            name = customList.substr(strpos, seppos-strpos);
-            name += " #";
-            name += std::to_string(++count);
+            auto check_match = [curitem](const DeviceEntry &entry) -> bool
+            { return entry.mName == curitem->mName; };
+            if(std::find_if(list.begin(), curitem, check_match) != curitem)
+            {
+                std::string name{curitem->mName};
+                size_t count{1};
+                auto check_name = [&name](const DeviceEntry &entry) -> bool
+                { return entry.mName == name; };
+                do {
+                    name = curitem->mName;
+                    name += " #";
+                    name += std::to_string(++count);
+                } while(std::find_if(list.begin(), curitem, check_name) != curitem);
+                curitem->mName = std::move(name);
+            }
         }
-
-        ++seppos;
-        list.emplace_back(DeviceEntry{std::move(name), customList.substr(seppos, nextpos-seppos)});
-        const auto &entry = list.back();
-        TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
-
-        if(nextpos != std::string::npos) ++nextpos;
-        strpos = nextpos;
     }
 }
 
 
 struct JackPlayback final : public BackendBase {
-    JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~JackPlayback() override;
 
+    int processRt(jack_nframes_t numframes) noexcept;
+    static int processRtC(jack_nframes_t numframes, void *arg) noexcept
+    { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
+
     int process(jack_nframes_t numframes) noexcept;
     static int processC(jack_nframes_t numframes, void *arg) noexcept
     { return static_cast<JackPlayback*>(arg)->process(numframes); }
@@ -227,6 +304,7 @@ struct JackPlayback final : public BackendBase {
     std::mutex mMutex;
 
     std::atomic<bool> mPlaying{false};
+    bool mRTMixing{false};
     RingBufferPtr mRing;
     al::semaphore mSem;
 
@@ -241,16 +319,40 @@ JackPlayback::~JackPlayback()
     if(!mClient)
         return;
 
-    std::for_each(mPort.begin(), mPort.end(),
-        [this](jack_port_t *port) -> void
-        { if(port) jack_port_unregister(mClient, port); }
-    );
+    auto unregister_port = [this](jack_port_t *port) -> void
+    { if(port) jack_port_unregister(mClient, port); };
+    std::for_each(mPort.begin(), mPort.end(), unregister_port);
     mPort.fill(nullptr);
+
     jack_client_close(mClient);
     mClient = nullptr;
 }
 
 
+int JackPlayback::processRt(jack_nframes_t numframes) noexcept
+{
+    std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+    size_t numchans{0};
+    for(auto port : mPort)
+    {
+        if(!port || numchans == mDevice->RealOut.Buffer.size())
+            break;
+        out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+    }
+
+    if LIKELY(mPlaying.load(std::memory_order_acquire))
+        mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
+    else
+    {
+        auto clear_buf = [numframes](float *outbuf) -> void
+        { std::fill_n(outbuf, numframes, 0.0f); };
+        std::for_each(out.begin(), out.begin()+numchans, clear_buf);
+    }
+
+    return 0;
+}
+
+
 int JackPlayback::process(jack_nframes_t numframes) noexcept
 {
     std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
@@ -352,49 +454,56 @@ int JackPlayback::mixerProc()
 
 void JackPlayback::open(const char *name)
 {
-    mPortPattern.clear();
-
-    if(!name)
-        name = jackDevice;
-    else if(strcmp(name, jackDevice) != 0)
+    if(!mClient)
     {
-        if(PlaybackList.empty())
-            EnumerateDevices(PlaybackList);
+        const PathNamePair &binname = GetProcBinary();
+        const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+
+        jack_status_t status;
+        mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
+        if(mClient == nullptr)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to open client connection: 0x%02x", status};
+        if((status&JackServerStarted))
+            TRACE("JACK server started\n");
+        if((status&JackNameNotUnique))
+        {
+            client_name = jack_get_client_name(mClient);
+            TRACE("Client name not unique, got '%s' instead\n", client_name);
+        }
+    }
+
+    if(PlaybackList.empty())
+        EnumerateDevices(mClient, PlaybackList);
 
+    if(!name && !PlaybackList.empty())
+    {
+        name = PlaybackList[0].mName.c_str();
+        mPortPattern = PlaybackList[0].mPattern;
+    }
+    else
+    {
         auto check_name = [name](const DeviceEntry &entry) -> bool
         { return entry.mName == name; };
         auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
         if(iter == PlaybackList.cend())
             throw al::backend_exception{al::backend_error::NoDevice,
-                "Device name \"%s\" not found", name};
+                "Device name \"%s\" not found", name?name:""};
         mPortPattern = iter->mPattern;
     }
 
-    const char *client_name{"alsoft"};
-    jack_status_t status;
-    mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
-    if(mClient == nullptr)
-        throw al::backend_exception{al::backend_error::DeviceError,
-            "Failed to open client connection: 0x%02x", status};
-
-    if((status&JackServerStarted))
-        TRACE("JACK server started\n");
-    if((status&JackNameNotUnique))
-    {
-        client_name = jack_get_client_name(mClient);
-        TRACE("Client name not unique, got '%s' instead\n", client_name);
-    }
-
-    jack_set_process_callback(mClient, &JackPlayback::processC, this);
+    mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
+    jack_set_process_callback(mClient,
+        mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
 
     mDevice->DeviceName = name;
 }
 
 bool JackPlayback::reset()
 {
-    std::for_each(mPort.begin(), mPort.end(),
-        [this](jack_port_t *port) -> void
-        { if(port) jack_port_unregister(mClient, port); });
+    auto unregister_port = [this](jack_port_t *port) -> void
+    { if(port) jack_port_unregister(mClient, port); };
+    std::for_each(mPort.begin(), mPort.end(), unregister_port);
     mPort.fill(nullptr);
 
     /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
@@ -402,28 +511,39 @@ bool JackPlayback::reset()
      */
     mDevice->Frequency = jack_get_sample_rate(mClient);
     mDevice->UpdateSize = jack_get_buffer_size(mClient);
-    mDevice->BufferSize = mDevice->UpdateSize * 2;
-
-    const char *devname{mDevice->DeviceName.c_str()};
-    uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
-    bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
-    mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+    if(mRTMixing)
+    {
+        /* Assume only two periods when directly mixing. Should try to query
+         * the total port latency when connected.
+         */
+        mDevice->BufferSize = mDevice->UpdateSize * 2;
+    }
+    else
+    {
+        const char *devname{mDevice->DeviceName.c_str()};
+        uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+        bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+        mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+    }
 
     /* Force 32-bit float output. */
     mDevice->FmtType = DevFmtFloat;
 
+    int port_num{0};
     auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
-    auto bad_port = std::find_if_not(mPort.begin(), ports_end,
-        [this](jack_port_t *&port) -> bool
-        {
-            std::string name{"channel_" + std::to_string(&port - &mPort[0] + 1)};
-            port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
-                JackPortIsOutput, 0);
-            return port != nullptr;
-        });
+    auto bad_port = mPort.begin();
+    while(bad_port != ports_end)
+    {
+        std::string name{"channel_" + std::to_string(++port_num)};
+        *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
+            JackPortIsOutput | JackPortIsTerminal, 0);
+        if(!*bad_port) break;
+        ++bad_port;
+    }
     if(bad_port != ports_end)
     {
-        ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
+        ERR("Failed to register enough JACK ports for %s output\n",
+            DevFmtChannelsString(mDevice->FmtChans));
         if(bad_port == mPort.begin()) return false;
 
         if(bad_port == mPort.begin()+1)
@@ -453,29 +573,25 @@ void JackPlayback::start()
     const char *devname{mDevice->DeviceName.c_str()};
     if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
     {
-        const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), nullptr,
-            JackPortIsPhysical|JackPortIsInput)};
-        if(ports == nullptr)
+        JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
+            JackPortIsInput)};
+        if(!pnames)
         {
             jack_deactivate(mClient);
-            throw al::backend_exception{al::backend_error::DeviceError,
-                "No physical playback ports found"};
+            throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
         }
-        auto connect_port = [this](const jack_port_t *port, const char *pname) -> bool
+
+        for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
         {
-            if(!port) return false;
-            if(!pname)
+            if(!pnames[i])
             {
-                ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
-                return false;
+                ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
+                break;
             }
-            if(jack_connect(mClient, jack_port_name(port), pname))
-                ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
-                    pname);
-            return true;
-        };
-        std::mismatch(mPort.begin(), mPort.end(), ports, connect_port);
-        jack_free(ports);
+            if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
+                ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
+                    pnames[i]);
+        }
     }
 
     /* Reconfigure buffer metrics in case the server changed it since the reset
@@ -486,36 +602,45 @@ void JackPlayback::start()
     mDevice->UpdateSize = jack_get_buffer_size(mClient);
     mDevice->BufferSize = mDevice->UpdateSize * 2;
 
-    uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
-    bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
-    mDevice->BufferSize = bufsize + mDevice->UpdateSize;
-
     mRing = nullptr;
-    mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
-
-    try {
+    if(mRTMixing)
         mPlaying.store(true, std::memory_order_release);
-        mKillNow.store(false, std::memory_order_release);
-        mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
-    }
-    catch(std::exception& e) {
-        jack_deactivate(mClient);
-        mPlaying.store(false, std::memory_order_release);
-        throw al::backend_exception{al::backend_error::DeviceError,
-            "Failed to start mixing thread: %s", e.what()};
+    else
+    {
+        uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+        bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+        mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+
+        mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
+
+        try {
+            mPlaying.store(true, std::memory_order_release);
+            mKillNow.store(false, std::memory_order_release);
+            mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
+        }
+        catch(std::exception& e) {
+            jack_deactivate(mClient);
+            mPlaying.store(false, std::memory_order_release);
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to start mixing thread: %s", e.what()};
+        }
     }
 }
 
 void JackPlayback::stop()
 {
-    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
-        return;
-
-    mSem.post();
-    mThread.join();
+    if(mPlaying.load(std::memory_order_acquire))
+    {
+        mKillNow.store(true, std::memory_order_release);
+        if(mThread.joinable())
+        {
+            mSem.post();
+            mThread.join();
+        }
 
-    jack_deactivate(mClient);
-    mPlaying.store(false, std::memory_order_release);
+        jack_deactivate(mClient);
+        mPlaying.store(false, std::memory_order_release);
+    }
 }
 
 
@@ -525,7 +650,7 @@ ClockLatency JackPlayback::getClockLatency()
 
     std::lock_guard<std::mutex> _{mMutex};
     ret.ClockTime = GetDeviceClockTime(mDevice);
-    ret.Latency  = std::chrono::seconds{mRing->readSpace()};
+    ret.Latency  = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
     ret.Latency /= mDevice->Frequency;
 
     return ret;
@@ -547,10 +672,13 @@ bool JackBackendFactory::init()
     if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
         ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
 
+    const PathNamePair &binname = GetProcBinary();
+    const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+
     void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
     jack_set_error_function(jack_msg_handler);
     jack_status_t status;
-    jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
+    jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)};
     jack_set_error_function(old_error_cb);
     if(!client)
     {
@@ -575,10 +703,20 @@ std::string JackBackendFactory::probe(BackendType type)
         /* Includes null char. */
         outnames.append(entry.mName.c_str(), entry.mName.length()+1);
     };
+
+    const PathNamePair &binname = GetProcBinary();
+    const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
+    jack_status_t status;
     switch(type)
     {
     case BackendType::Playback:
-        EnumerateDevices(PlaybackList);
+        if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
+        {
+            EnumerateDevices(client, PlaybackList);
+            jack_client_close(client);
+        }
+        else
+            WARN("jack_client_open() failed, 0x%02x\n", status);
         std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
         break;
     case BackendType::Capture:
@@ -587,7 +725,7 @@ std::string JackBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new JackPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/jack.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_JACK_H
 #define BACKENDS_JACK_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct JackBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 6 - 7
libs/openal-soft/Alc/backends/loopback.cpp

@@ -20,18 +20,17 @@
 
 #include "config.h"
 
-#include "backends/loopback.h"
+#include "loopback.h"
 
-#include "alcmain.h"
-#include "alu.h"
+#include "core/device.h"
 
 
 namespace {
 
 struct LoopbackBackend final : public BackendBase {
-    LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
 
-    void open(const ALCchar *name) override;
+    void open(const char *name) override;
     bool reset() override;
     void start() override;
     void stop() override;
@@ -40,7 +39,7 @@ struct LoopbackBackend final : public BackendBase {
 };
 
 
-void LoopbackBackend::open(const ALCchar *name)
+void LoopbackBackend::open(const char *name)
 {
     mDevice->DeviceName = name;
 }
@@ -69,7 +68,7 @@ bool LoopbackBackendFactory::querySupport(BackendType)
 std::string LoopbackBackendFactory::probe(BackendType)
 { return std::string{}; }
 
-BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
+BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
 { return BackendPtr{new LoopbackBackend{device}}; }
 
 BackendFactory &LoopbackBackendFactory::getFactory()

+ 2 - 2
libs/openal-soft/Alc/backends/loopback.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_LOOPBACK_H
 #define BACKENDS_LOOPBACK_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct LoopbackBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 5 - 5
libs/openal-soft/Alc/backends/null.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/null.h"
+#include "null.h"
 
 #include <exception>
 #include <atomic>
@@ -30,9 +30,9 @@
 #include <functional>
 #include <thread>
 
-#include "alcmain.h"
+#include "core/device.h"
 #include "almalloc.h"
-#include "alu.h"
+#include "core/helpers.h"
 #include "threads.h"
 
 
@@ -46,7 +46,7 @@ constexpr char nullDevice[] = "No Output";
 
 
 struct NullBackend final : public BackendBase {
-    NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
 
     int mixerProc();
 
@@ -165,7 +165,7 @@ std::string NullBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new NullBackend{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/null.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_NULL_H
 #define BACKENDS_NULL_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct NullBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 55 - 54
libs/openal-soft/Alc/backends/oboe.cpp

@@ -5,8 +5,10 @@
 
 #include <cassert>
 #include <cstring>
+#include <stdint.h>
 
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/device.h"
 #include "core/logging.h"
 
 #include "oboe/Oboe.h"
@@ -18,7 +20,7 @@ constexpr char device_name[] = "Oboe Default";
 
 
 struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
-    OboePlayback(ALCdevice *device) : BackendBase{device} { }
+    OboePlayback(DeviceBase *device) : BackendBase{device} { }
 
     oboe::ManagedStream mStream;
 
@@ -38,17 +40,6 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea
     assert(numFrames > 0);
     const int32_t numChannels{oboeStream->getChannelCount()};
 
-    if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo)
-    {
-        /* If the device is only mixing stereo but there's more than two
-         * output channels, there are unused channels that need to be silenced.
-         */
-        if(mStream->getFormat() == oboe::AudioFormat::Float)
-            memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float));
-        else
-            memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t));
-    }
-
     mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
         static_cast<uint32_t>(numChannels));
     return oboe::DataCallbackResult::Continue;
@@ -64,9 +55,10 @@ void OboePlayback::open(const char *name)
             name};
 
     /* Open a basic output stream, just to ensure it can work. */
+    oboe::ManagedStream stream;
     oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
         ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
-        ->openManagedStream(mStream)};
+        ->openManagedStream(stream)};
     if(result != oboe::Result::OK)
         throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
             oboe::convertToText(result)};
@@ -139,38 +131,15 @@ bool OboePlayback::reset()
         mStream->getBufferCapacityInFrames()));
     TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
 
-    switch(mStream->getChannelCount())
+    if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
     {
-    case oboe::ChannelCount::Mono:
-        mDevice->FmtChans = DevFmtMono;
-        break;
-    case oboe::ChannelCount::Stereo:
-        mDevice->FmtChans = DevFmtStereo;
-        break;
-    /* Other potential configurations. Could be wrong, but better than failing.
-     * Assume WFX channel order.
-     */
-    case 4:
-        mDevice->FmtChans = DevFmtQuad;
-        break;
-    case 6:
-        mDevice->FmtChans = DevFmtX51Rear;
-        break;
-    case 7:
-        mDevice->FmtChans = DevFmtX61;
-        break;
-    case 8:
-        mDevice->FmtChans = DevFmtX71;
-        break;
-    default:
-        if(mStream->getChannelCount() < 1)
+        if(mStream->getChannelCount() >= 2)
+            mDevice->FmtChans = DevFmtStereo;
+        else if(mStream->getChannelCount() == 1)
+            mDevice->FmtChans = DevFmtMono;
+        else
             throw al::backend_exception{al::backend_error::DeviceError,
                 "Got unhandled channel count: %d", mStream->getChannelCount()};
-        /* Assume first two channels are front left/right. We can do a stereo
-         * mix and keep the other channels silent.
-         */
-        mDevice->FmtChans = DevFmtStereo;
-        break;
     }
     setDefaultWFXChannelOrder();
 
@@ -220,10 +189,13 @@ void OboePlayback::stop()
 
 
 struct OboeCapture final : public BackendBase {
-    OboeCapture(ALCdevice *device) : BackendBase{device} { }
+    OboeCapture(DeviceBase *device) : BackendBase{device} { }
 
     oboe::ManagedStream mStream;
 
+    std::vector<al::byte> mSamples;
+    uint mLastAvail{0u};
+
     void open(const char *name) override;
     void start() override;
     void stop() override;
@@ -260,7 +232,6 @@ void OboeCapture::open(const char *name)
         break;
     case DevFmtQuad:
     case DevFmtX51:
-    case DevFmtX51Rear:
     case DevFmtX61:
     case DevFmtX71:
     case DevFmtAmbi3D:
@@ -321,6 +292,23 @@ void OboeCapture::start()
 
 void OboeCapture::stop()
 {
+    /* Capture any unread samples before stopping. Oboe drops whatever's left
+     * in the stream.
+     */
+    if(auto availres = mStream->getAvailableFrames())
+    {
+        const auto avail = std::max(static_cast<uint>(availres.value()), mLastAvail);
+        const size_t frame_size{static_cast<uint32_t>(mStream->getBytesPerFrame())};
+        const size_t pos{mSamples.size()};
+        mSamples.resize(pos + avail*frame_size);
+
+        auto result = mStream->read(&mSamples[pos], availres.value(), 0);
+        uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
+        if(got < avail)
+            std::fill_n(&mSamples[pos + got*frame_size], (avail-got)*frame_size, al::byte{});
+        mLastAvail = 0;
+    }
+
     const oboe::Result result{mStream->stop()};
     if(result != oboe::Result::OK)
         throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
@@ -329,23 +317,36 @@ void OboeCapture::stop()
 
 uint OboeCapture::availableSamples()
 {
-    auto result = mStream->getAvailableFrames();
-    /* FIXME: This shouldn't report less samples than have been previously
-     * reported and not captured.
+    /* Keep track of the max available frame count, to ensure it doesn't go
+     * backwards.
      */
-    if(!result) return 0;
-    return static_cast<uint>(result.value());
+    if(auto result = mStream->getAvailableFrames())
+        mLastAvail = std::max(static_cast<uint>(result.value()), mLastAvail);
+
+    const auto frame_size = static_cast<uint32_t>(mStream->getBytesPerFrame());
+    return static_cast<uint>(mSamples.size()/frame_size) + mLastAvail;
 }
 
 void OboeCapture::captureSamples(al::byte *buffer, uint samples)
 {
+    const auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
+    if(const size_t storelen{mSamples.size()})
+    {
+        const auto instore = static_cast<uint>(storelen / frame_size);
+        const uint tocopy{std::min(samples, instore) * frame_size};
+        std::copy_n(mSamples.begin(), tocopy, buffer);
+        mSamples.erase(mSamples.begin(), mSamples.begin() + tocopy);
+
+        buffer += tocopy;
+        samples -= tocopy/frame_size;
+        if(!samples) return;
+    }
+
     auto result = mStream->read(buffer, static_cast<int32_t>(samples), 0);
     uint got{bool{result} ? static_cast<uint>(result.value()) : 0u};
     if(got < samples)
-    {
-        auto frame_size = static_cast<uint>(mStream->getBytesPerFrame());
         std::fill_n(buffer + got*frame_size, (samples-got)*frame_size, al::byte{});
-    }
+    mLastAvail = std::max(mLastAvail, samples) - samples;
 }
 
 } // namespace
@@ -367,7 +368,7 @@ std::string OboeBackendFactory::probe(BackendType type)
     return std::string{};
 }
 
-BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new OboePlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/oboe.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_OBOE_H
 #define BACKENDS_OBOE_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct OboeBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 23 - 14
libs/openal-soft/Alc/backends/opensl.cpp

@@ -21,7 +21,7 @@
 
 #include "config.h"
 
-#include "backends/opensl.h"
+#include "opensl.h"
 
 #include <stdlib.h>
 #include <jni.h>
@@ -33,9 +33,9 @@
 #include <functional>
 
 #include "albit.h"
-#include "alcmain.h"
-#include "alu.h"
-#include "compat.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "opthelpers.h"
 #include "ringbuffer.h"
@@ -68,9 +68,6 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
     case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
         SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
         SL_SPEAKER_SIDE_RIGHT;
-    case DevFmtX51Rear: 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;
     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;
@@ -105,7 +102,7 @@ constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
 
 constexpr SLuint32 GetByteOrderEndianness() noexcept
 {
-    if_constexpr(al::endian::native == al::endian::little)
+    if(al::endian::native == al::endian::little)
         return SL_BYTEORDER_LITTLEENDIAN;
     return SL_BYTEORDER_BIGENDIAN;
 }
@@ -151,7 +148,7 @@ const char *res_str(SLresult result) noexcept
 
 
 struct OpenSLPlayback final : public BackendBase {
-    OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~OpenSLPlayback() override;
 
     void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@@ -316,6 +313,9 @@ void OpenSLPlayback::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
+    /* There's only one device, so if it's already open, there's nothing to do. */
+    if(mEngineObj) return;
+
     // create engine
     SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
     PRINTERR(result, "slCreateEngine");
@@ -629,7 +629,7 @@ ClockLatency OpenSLPlayback::getClockLatency()
 
 
 struct OpenSLCapture final : public BackendBase {
-    OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~OpenSLCapture() override;
 
     void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
@@ -810,8 +810,11 @@ void OpenSLCapture::open(const char* name)
     if(SL_RESULT_SUCCESS == result)
     {
         const uint chunk_size{mDevice->UpdateSize * mFrameSize};
+        const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
 
         auto data = mRing->getWriteVector();
+        std::fill_n(data.first.buf, data.first.len*chunk_size, silence);
+        std::fill_n(data.second.buf, data.second.len*chunk_size, silence);
         for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
         {
             result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
@@ -875,6 +878,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
 {
     const uint update_size{mDevice->UpdateSize};
     const uint chunk_size{update_size * mFrameSize};
+    const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0};
 
     /* Read the desired samples from the ring buffer then advance its read
      * pointer.
@@ -922,15 +926,20 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
     {
         SLresult result{SL_RESULT_SUCCESS};
         auto wdata = mRing->getWriteVector();
+        std::fill_n(wdata.first.buf, wdata.first.len*chunk_size, silence);
         for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++)
         {
             result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
             PRINTERR(result, "bufferQueue->Enqueue");
         }
-        for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
+        if(wdata.second.len > 0)
         {
-            result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
+            std::fill_n(wdata.second.buf, wdata.second.len*chunk_size, silence);
+            for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
+            {
+                result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
+                PRINTERR(result, "bufferQueue->Enqueue");
+            }
         }
     }
 }
@@ -959,7 +968,7 @@ std::string OSLBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new OpenSLPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/opensl.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_OSL_H
 #define BACKENDS_OSL_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct OSLBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 14 - 10
libs/openal-soft/Alc/backends/oss.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/oss.h"
+#include "oss.h"
 
 #include <fcntl.h>
 #include <poll.h>
@@ -41,13 +41,13 @@
 #include <thread>
 #include <utility>
 
-#include "alcmain.h"
-#include "alconfig.h"
 #include "albyte.h"
+#include "alc/alconfig.h"
 #include "almalloc.h"
 #include "alnumeric.h"
 #include "aloptional.h"
-#include "alu.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
 #include "threads.h"
@@ -226,7 +226,7 @@ uint log2i(uint x)
 
 
 struct OSSPlayback final : public BackendBase {
-    OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~OSSPlayback() override;
 
     int mixerProc();
@@ -249,7 +249,7 @@ struct OSSPlayback final : public BackendBase {
 OSSPlayback::~OSSPlayback()
 {
     if(mFd != -1)
-        close(mFd);
+        ::close(mFd);
     mFd = -1;
 }
 
@@ -328,11 +328,15 @@ void OSSPlayback::open(const char *name)
         devname = iter->device_name.c_str();
     }
 
-    mFd = ::open(devname, O_WRONLY);
-    if(mFd == -1)
+    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)};
 
+    if(mFd != -1)
+        ::close(mFd);
+    mFd = fd;
+
     mDevice->DeviceName = name;
 }
 
@@ -438,7 +442,7 @@ void OSSPlayback::stop()
 
 
 struct OSScapture final : public BackendBase {
-    OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    OSScapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~OSScapture() override;
 
     int recordProc();
@@ -676,7 +680,7 @@ std::string OSSBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new OSSPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/oss.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_OSS_H
 #define BACKENDS_OSS_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct OSSBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 2008 - 0
libs/openal-soft/Alc/backends/pipewire.cpp

@@ -0,0 +1,2008 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2010 by Chris Robinson
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "pipewire.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <cerrno>
+#include <chrono>
+#include <ctime>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <stdint.h>
+#include <type_traits>
+#include <utility>
+
+#include "albyte.h"
+#include "alc/alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/helpers.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+
+/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */
+_Pragma("GCC diagnostic push")
+_Pragma("GCC diagnostic ignored \"-Weverything\"")
+#include "pipewire/pipewire.h"
+#include "pipewire/extensions/metadata.h"
+#include "spa/buffer/buffer.h"
+#include "spa/param/audio/format-utils.h"
+#include "spa/param/audio/raw.h"
+#include "spa/param/param.h"
+#include "spa/pod/builder.h"
+#include "spa/utils/json.h"
+
+namespace {
+/* Wrap some nasty macros here too... */
+template<typename ...Args>
+auto ppw_core_add_listener(pw_core *core, Args&& ...args)
+{ return pw_core_add_listener(core, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_core_sync(pw_core *core, Args&& ...args)
+{ return pw_core_sync(core, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args)
+{ return pw_registry_add_listener(reg, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_node_add_listener(pw_node *node, Args&& ...args)
+{ return pw_node_add_listener(node, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_node_subscribe_params(pw_node *node, Args&& ...args)
+{ return pw_node_subscribe_params(node, std::forward<Args>(args)...); }
+template<typename ...Args>
+auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args)
+{ return pw_metadata_add_listener(mdata, std::forward<Args>(args)...); }
+
+
+constexpr auto get_pod_type(const spa_pod *pod) noexcept
+{ return SPA_POD_TYPE(pod); }
+
+template<typename T>
+constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept
+{ return al::span<T>{static_cast<T*>(SPA_POD_BODY(pod)), count}; }
+template<typename T, size_t N>
+constexpr auto get_pod_body(const spa_pod *pod) noexcept
+{ return al::span<T,N>{static_cast<T*>(SPA_POD_BODY(pod)), N}; }
+
+constexpr auto make_pod_builder(void *data, uint32_t size) noexcept
+{ return SPA_POD_BUILDER_INIT(data, size); }
+
+constexpr auto get_array_value_type(const spa_pod *pod) noexcept
+{ return SPA_POD_ARRAY_VALUE_TYPE(pod); }
+
+constexpr auto PwIdAny = PW_ID_ANY;
+
+} // namespace
+_Pragma("GCC diagnostic pop")
+
+namespace {
+
+using std::chrono::seconds;
+using std::chrono::nanoseconds;
+using uint = unsigned int;
+
+constexpr char pwireDevice[] = "PipeWire Output";
+constexpr char pwireInput[] = "PipeWire Input";
+
+
+#ifdef HAVE_DYNLOAD
+#define PWIRE_FUNCS(MAGIC)                                                    \
+    MAGIC(pw_context_connect)                                                 \
+    MAGIC(pw_context_destroy)                                                 \
+    MAGIC(pw_context_new)                                                     \
+    MAGIC(pw_core_disconnect)                                                 \
+    MAGIC(pw_init)                                                            \
+    MAGIC(pw_properties_free)                                                 \
+    MAGIC(pw_properties_new)                                                  \
+    MAGIC(pw_properties_set)                                                  \
+    MAGIC(pw_properties_setf)                                                 \
+    MAGIC(pw_proxy_add_object_listener)                                       \
+    MAGIC(pw_proxy_destroy)                                                   \
+    MAGIC(pw_proxy_get_user_data)                                             \
+    MAGIC(pw_stream_add_listener)                                             \
+    MAGIC(pw_stream_connect)                                                  \
+    MAGIC(pw_stream_dequeue_buffer)                                           \
+    MAGIC(pw_stream_destroy)                                                  \
+    MAGIC(pw_stream_get_state)                                                \
+    MAGIC(pw_stream_get_time)                                                 \
+    MAGIC(pw_stream_new)                                                      \
+    MAGIC(pw_stream_queue_buffer)                                             \
+    MAGIC(pw_stream_set_active)                                               \
+    MAGIC(pw_thread_loop_new)                                                 \
+    MAGIC(pw_thread_loop_destroy)                                             \
+    MAGIC(pw_thread_loop_get_loop)                                            \
+    MAGIC(pw_thread_loop_start)                                               \
+    MAGIC(pw_thread_loop_stop)                                                \
+    MAGIC(pw_thread_loop_lock)                                                \
+    MAGIC(pw_thread_loop_wait)                                                \
+    MAGIC(pw_thread_loop_signal)                                              \
+    MAGIC(pw_thread_loop_unlock)                                              \
+
+void *pwire_handle;
+#define MAKE_FUNC(f) decltype(f) * p##f;
+PWIRE_FUNCS(MAKE_FUNC)
+#undef MAKE_FUNC
+
+bool pwire_load()
+{
+    if(pwire_handle)
+        return true;
+
+    static constexpr char pwire_library[] = "libpipewire-0.3.so.0";
+    std::string missing_funcs;
+
+    pwire_handle = LoadLib(pwire_library);
+    if(!pwire_handle)
+    {
+        WARN("Failed to load %s\n", pwire_library);
+        return false;
+    }
+
+#define LOAD_FUNC(f) do {                                                     \
+    p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pwire_handle, #f));     \
+    if(p##f == nullptr) missing_funcs += "\n" #f;                             \
+} while(0);
+    PWIRE_FUNCS(LOAD_FUNC)
+#undef LOAD_FUNC
+
+    if(!missing_funcs.empty())
+    {
+        WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+        CloseLib(pwire_handle);
+        pwire_handle = nullptr;
+        return false;
+    }
+
+    return true;
+}
+
+#ifndef IN_IDE_PARSER
+#define pw_context_connect ppw_context_connect
+#define pw_context_destroy ppw_context_destroy
+#define pw_context_new ppw_context_new
+#define pw_core_disconnect ppw_core_disconnect
+#define pw_init ppw_init
+#define pw_properties_free ppw_properties_free
+#define pw_properties_new ppw_properties_new
+#define pw_properties_set ppw_properties_set
+#define pw_properties_setf ppw_properties_setf
+#define pw_proxy_add_object_listener ppw_proxy_add_object_listener
+#define pw_proxy_destroy ppw_proxy_destroy
+#define pw_proxy_get_user_data ppw_proxy_get_user_data
+#define pw_stream_add_listener ppw_stream_add_listener
+#define pw_stream_connect ppw_stream_connect
+#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
+#define pw_stream_destroy ppw_stream_destroy
+#define pw_stream_get_state ppw_stream_get_state
+#define pw_stream_get_time ppw_stream_get_time
+#define pw_stream_new ppw_stream_new
+#define pw_stream_queue_buffer ppw_stream_queue_buffer
+#define pw_stream_set_active ppw_stream_set_active
+#define pw_thread_loop_destroy ppw_thread_loop_destroy
+#define pw_thread_loop_get_loop ppw_thread_loop_get_loop
+#define pw_thread_loop_lock ppw_thread_loop_lock
+#define pw_thread_loop_new ppw_thread_loop_new
+#define pw_thread_loop_signal ppw_thread_loop_signal
+#define pw_thread_loop_start ppw_thread_loop_start
+#define pw_thread_loop_stop ppw_thread_loop_stop
+#define pw_thread_loop_unlock ppw_thread_loop_unlock
+#define pw_thread_loop_wait ppw_thread_loop_wait
+#endif
+
+#else
+
+constexpr bool pwire_load() { return true; }
+#endif
+
+/* Helpers for retrieving values from params */
+template<uint32_t T> struct PodInfo { };
+
+template<>
+struct PodInfo<SPA_TYPE_Int> {
+    using Type = int32_t;
+    static auto get_value(const spa_pod *pod, int32_t *val)
+    { return spa_pod_get_int(pod, val); }
+};
+template<>
+struct PodInfo<SPA_TYPE_Id> {
+    using Type = uint32_t;
+    static auto get_value(const spa_pod *pod, uint32_t *val)
+    { return spa_pod_get_id(pod, val); }
+};
+
+template<uint32_t T>
+using Pod_t = typename PodInfo<T>::Type;
+
+template<uint32_t T>
+al::span<const Pod_t<T>> get_array_span(const spa_pod *pod)
+{
+    uint32_t nvals;
+    if(void *v{spa_pod_get_array(pod, &nvals)})
+    {
+        if(get_array_value_type(pod) == T)
+            return {static_cast<const Pod_t<T>*>(v), nvals};
+    }
+    return {};
+}
+
+template<uint32_t T>
+al::optional<Pod_t<T>> get_value(const spa_pod *value)
+{
+    Pod_t<T> val{};
+    if(PodInfo<T>::get_value(value, &val) == 0)
+        return val;
+    return al::nullopt;
+}
+
+/* Internally, PipeWire types "inherit" from each other, but this is hidden
+ * from the API and the caller is expected to C-style cast to inherited types
+ * as needed. It's also not made very clear what types a given type can be
+ * casted to. To make it a bit safer, this as() method allows casting pw_*
+ * types to known inherited types, generating a compile-time error for
+ * unexpected/invalid casts.
+ */
+template<typename To, typename From>
+To as(From) noexcept = delete;
+
+/* pw_proxy
+ * - pw_registry
+ * - pw_node
+ * - pw_metadata
+ */
+template<>
+pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast<pw_proxy*>(reg); }
+template<>
+pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast<pw_proxy*>(node); }
+template<>
+pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast<pw_proxy*>(mdata); }
+
+
+struct PwContextDeleter {
+    void operator()(pw_context *context) const { pw_context_destroy(context); }
+};
+using PwContextPtr = std::unique_ptr<pw_context,PwContextDeleter>;
+
+struct PwCoreDeleter {
+    void operator()(pw_core *core) const { pw_core_disconnect(core); }
+};
+using PwCorePtr = std::unique_ptr<pw_core,PwCoreDeleter>;
+
+struct PwRegistryDeleter {
+    void operator()(pw_registry *reg) const { pw_proxy_destroy(as<pw_proxy*>(reg)); }
+};
+using PwRegistryPtr = std::unique_ptr<pw_registry,PwRegistryDeleter>;
+
+struct PwNodeDeleter {
+    void operator()(pw_node *node) const { pw_proxy_destroy(as<pw_proxy*>(node)); }
+};
+using PwNodePtr = std::unique_ptr<pw_node,PwNodeDeleter>;
+
+struct PwMetadataDeleter {
+    void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as<pw_proxy*>(mdata)); }
+};
+using PwMetadataPtr = std::unique_ptr<pw_metadata,PwMetadataDeleter>;
+
+struct PwStreamDeleter {
+    void operator()(pw_stream *stream) const { pw_stream_destroy(stream); }
+};
+using PwStreamPtr = std::unique_ptr<pw_stream,PwStreamDeleter>;
+
+/* Enums for bitflags... again... *sigh* */
+constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept
+{ return static_cast<pw_stream_flags>(lhs | std::underlying_type_t<pw_stream_flags>{rhs}); }
+
+class ThreadMainloop {
+    pw_thread_loop *mLoop{};
+
+public:
+    ThreadMainloop() = default;
+    ThreadMainloop(const ThreadMainloop&) = delete;
+    ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; }
+    explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { }
+    ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); }
+
+    ThreadMainloop& operator=(const ThreadMainloop&) = delete;
+    ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept
+    { std::swap(mLoop, rhs.mLoop); return *this; }
+    ThreadMainloop& operator=(std::nullptr_t) noexcept
+    {
+        if(mLoop)
+            pw_thread_loop_destroy(mLoop);
+        mLoop = nullptr;
+        return *this;
+    }
+
+    explicit operator bool() const noexcept { return mLoop != nullptr; }
+
+    auto start() const { return pw_thread_loop_start(mLoop); }
+    auto stop() const { return pw_thread_loop_stop(mLoop); }
+
+    auto getLoop() const { return pw_thread_loop_get_loop(mLoop); }
+
+    auto lock() const { return pw_thread_loop_lock(mLoop); }
+    auto unlock() const { return pw_thread_loop_unlock(mLoop); }
+
+    auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); }
+
+    auto newContext(pw_properties *props=nullptr, size_t user_data_size=0)
+    { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; }
+
+    static auto Create(const char *name, spa_dict *props=nullptr)
+    { return ThreadMainloop{pw_thread_loop_new(name, props)}; }
+
+    friend struct MainloopUniqueLock;
+};
+struct MainloopUniqueLock : public std::unique_lock<ThreadMainloop> {
+    using std::unique_lock<ThreadMainloop>::unique_lock;
+    MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default;
+
+    auto wait() const -> void
+    { pw_thread_loop_wait(mutex()->mLoop); }
+
+    template<typename Predicate>
+    auto wait(Predicate done_waiting) const -> void
+    { while(!done_waiting()) wait(); }
+};
+using MainloopLockGuard = std::lock_guard<ThreadMainloop>;
+
+
+/* There's quite a mess here, but the purpose is to track active devices and
+ * their default formats, so playback devices can be configured to match. The
+ * device list is updated asynchronously, so it will have the latest list of
+ * devices provided by the server.
+ */
+
+struct NodeProxy;
+struct MetadataProxy;
+
+/* The global thread watching for global events. This particular class responds
+ * to objects being added to or removed from the registry.
+ */
+struct EventManager {
+    ThreadMainloop mLoop{};
+    PwContextPtr mContext{};
+    PwCorePtr mCore{};
+    PwRegistryPtr mRegistry{};
+    spa_hook mRegistryListener{};
+    spa_hook mCoreListener{};
+
+    /* A list of proxy objects watching for events about changes to objects in
+     * the registry.
+     */
+    std::vector<NodeProxy*> mNodeList;
+    MetadataProxy *mDefaultMetadata{nullptr};
+
+    /* Initialization handling. When init() is called, mInitSeq is set to a
+     * SequenceID that marks the end of populating the registry. As objects of
+     * interest are found, events to parse them are generated and mInitSeq is
+     * updated with a newer ID. When mInitSeq stops being updated and the event
+     * corresponding to it is reached, mInitDone will be set to true.
+     */
+    std::atomic<bool> mInitDone{false};
+    std::atomic<bool> mHasAudio{false};
+    int mInitSeq{};
+
+    bool init();
+    ~EventManager();
+
+    void kill();
+
+    auto lock() const { return mLoop.lock(); }
+    auto unlock() const { return mLoop.unlock(); }
+
+    /**
+     * Waits for initialization to finish. The event manager must *NOT* be
+     * locked when calling this.
+     */
+    void waitForInit()
+    {
+        if(unlikely(!mInitDone.load(std::memory_order_acquire)))
+        {
+            MainloopUniqueLock plock{mLoop};
+            plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); });
+        }
+    }
+
+    /**
+     * Waits for audio support to be detected, or initialization to finish,
+     * whichever is first. Returns true if audio support was detected. The
+     * event manager must *NOT* be locked when calling this.
+     */
+    bool waitForAudio()
+    {
+        MainloopUniqueLock plock{mLoop};
+        bool has_audio{};
+        plock.wait([this,&has_audio]()
+        {
+            has_audio = mHasAudio.load(std::memory_order_acquire);
+            return has_audio || mInitDone.load(std::memory_order_acquire);
+        });
+        return has_audio;
+    }
+
+    void syncInit()
+    {
+        /* If initialization isn't done, update the sequence ID so it won't
+         * complete until after currently scheduled events.
+         */
+        if(!mInitDone.load(std::memory_order_relaxed))
+            mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq);
+    }
+
+    void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version,
+        const spa_dict *props);
+    static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type,
+        uint32_t version, const spa_dict *props)
+    { static_cast<EventManager*>(object)->addCallback(id, permissions, type, version, props); }
+
+    void removeCallback(uint32_t id);
+    static void removeCallbackC(void *object, uint32_t id)
+    { static_cast<EventManager*>(object)->removeCallback(id); }
+
+    static constexpr pw_registry_events CreateRegistryEvents()
+    {
+        pw_registry_events ret{};
+        ret.version = PW_VERSION_REGISTRY_EVENTS;
+        ret.global = &EventManager::addCallbackC;
+        ret.global_remove = &EventManager::removeCallbackC;
+        return ret;
+    }
+
+    void coreCallback(uint32_t id, int seq);
+    static void coreCallbackC(void *object, uint32_t id, int seq)
+    { static_cast<EventManager*>(object)->coreCallback(id, seq); }
+
+    static constexpr pw_core_events CreateCoreEvents()
+    {
+        pw_core_events ret{};
+        ret.version = PW_VERSION_CORE_EVENTS;
+        ret.done = &EventManager::coreCallbackC;
+        return ret;
+    }
+};
+using EventWatcherUniqueLock = std::unique_lock<EventManager>;
+using EventWatcherLockGuard = std::lock_guard<EventManager>;
+
+EventManager gEventHandler;
+
+/* Enumerated devices. This is updated asynchronously as the app runs, and the
+ * gEventHandler thread loop must be locked when accessing the list.
+ */
+enum class NodeType : unsigned char {
+    Sink, Source, Duplex
+};
+constexpr auto InvalidChannelConfig = DevFmtChannels(255);
+struct DeviceNode {
+    std::string mName;
+    std::string mDevName;
+
+    uint32_t mId{};
+    NodeType mType{};
+    bool mIsHeadphones{};
+    bool mIs51Rear{};
+
+    uint mSampleRate{};
+    DevFmtChannels mChannels{InvalidChannelConfig};
+
+    static std::vector<DeviceNode> sList;
+    static DeviceNode &Add(uint32_t id);
+    static DeviceNode *Find(uint32_t id);
+    static void Remove(uint32_t id);
+    static std::vector<DeviceNode> &GetList() noexcept { return sList; }
+
+    void parseSampleRate(const spa_pod *value) noexcept;
+    void parsePositions(const spa_pod *value) noexcept;
+    void parseChannelCount(const spa_pod *value) noexcept;
+};
+std::vector<DeviceNode> DeviceNode::sList;
+std::string DefaultSinkDevice;
+std::string DefaultSourceDevice;
+
+const char *AsString(NodeType type) noexcept
+{
+    switch(type)
+    {
+    case NodeType::Sink: return "sink";
+    case NodeType::Source: return "source";
+    case NodeType::Duplex: return "duplex";
+    }
+    return "<unknown>";
+}
+
+DeviceNode &DeviceNode::Add(uint32_t id)
+{
+    auto match_id = [id](DeviceNode &n) noexcept -> bool
+    { return n.mId == id; };
+
+    /* If the node is already in the list, return the existing entry. */
+    auto match = std::find_if(sList.begin(), sList.end(), match_id);
+    if(match != sList.end()) return *match;
+
+    sList.emplace_back();
+    auto &n = sList.back();
+    n.mId = id;
+    return n;
+}
+
+DeviceNode *DeviceNode::Find(uint32_t id)
+{
+    auto match_id = [id](DeviceNode &n) noexcept -> bool
+    { return n.mId == id; };
+
+    auto match = std::find_if(sList.begin(), sList.end(), match_id);
+    if(match != sList.end()) return std::addressof(*match);
+
+    return nullptr;
+}
+
+void DeviceNode::Remove(uint32_t id)
+{
+    auto match_id = [id](DeviceNode &n) noexcept -> bool
+    {
+        if(n.mId != id)
+            return false;
+        TRACE("Removing device \"%s\"\n", n.mDevName.c_str());
+        return true;
+    };
+
+    auto end = std::remove_if(sList.begin(), sList.end(), match_id);
+    sList.erase(end, sList.end());
+}
+
+
+const spa_audio_channel MonoMap[]{
+    SPA_AUDIO_CHANNEL_MONO
+}, StereoMap[] {
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR
+}, QuadMap[]{
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
+}, X51Map[]{
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+    SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+}, X51RearMap[]{
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+    SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR
+}, X61Map[]{
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+    SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+}, X71Map[]{
+    SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
+    SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR
+};
+
+/**
+ * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal
+ * to or a superset of map1).
+ */
+template<size_t N>
+bool MatchChannelMap(const al::span<const uint32_t> map0, const spa_audio_channel (&map1)[N])
+{
+    if(map0.size() < N)
+        return false;
+    for(const spa_audio_channel chid : map1)
+    {
+        if(std::find(map0.begin(), map0.end(), chid) == map0.end())
+            return false;
+    }
+    return true;
+}
+
+void DeviceNode::parseSampleRate(const spa_pod *value) noexcept
+{
+    /* TODO: Can this be anything else? Long, Float, Double? */
+    uint32_t nvals{}, choiceType{};
+    value = spa_pod_get_values(value, &nvals, &choiceType);
+
+    const uint podType{get_pod_type(value)};
+    if(podType != SPA_TYPE_Int)
+    {
+        WARN("Unhandled sample rate POD type: %u\n", podType);
+        return;
+    }
+
+    if(choiceType == SPA_CHOICE_Range)
+    {
+        if(nvals != 3)
+        {
+            WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals);
+            return;
+        }
+        auto srates = get_pod_body<int32_t,3>(value);
+
+        /* [0] is the default, [1] is the min, and [2] is the max. */
+        TRACE("Device ID %u sample rate: %d (range: %d -> %d)\n", mId, srates[0], srates[1],
+            srates[2]);
+        mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
+        return;
+    }
+
+    if(choiceType == SPA_CHOICE_Enum)
+    {
+        if(nvals == 0)
+        {
+            WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals);
+            return;
+        }
+        auto srates = get_pod_body<int32_t>(value, nvals);
+
+        /* [0] is the default, [1...size()-1] are available selections. */
+        std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}};
+        for(size_t i{2};i < srates.size();++i)
+        {
+            others += ", ";
+            others += std::to_string(srates[i]);
+        }
+        TRACE("Device ID %u sample rate: %d (%s)\n", mId, srates[0], others.c_str());
+        /* Pick the first rate listed that's within the allowed range (default
+         * rate if possible).
+         */
+        for(const auto &rate : srates)
+        {
+            if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE)
+            {
+                mSampleRate = static_cast<uint>(rate);
+                break;
+            }
+        }
+        return;
+    }
+
+    if(choiceType == SPA_CHOICE_None)
+    {
+        if(nvals != 1)
+        {
+            WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals);
+            return;
+        }
+        auto srates = get_pod_body<int32_t,1>(value);
+
+        TRACE("Device ID %u sample rate: %d\n", mId, srates[0]);
+        mSampleRate = static_cast<uint>(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE));
+        return;
+    }
+
+    WARN("Unhandled sample rate choice type: %u\n", choiceType);
+}
+
+void DeviceNode::parsePositions(const spa_pod *value) noexcept
+{
+    const auto chanmap = get_array_span<SPA_TYPE_Id>(value);
+    if(chanmap.empty()) return;
+
+    mIs51Rear = false;
+
+    if(MatchChannelMap(chanmap, X71Map))
+        mChannels = DevFmtX71;
+    else if(MatchChannelMap(chanmap, X61Map))
+        mChannels = DevFmtX61;
+    else if(MatchChannelMap(chanmap, X51Map))
+        mChannels = DevFmtX51;
+    else if(MatchChannelMap(chanmap, X51RearMap))
+    {
+        mChannels = DevFmtX51;
+        mIs51Rear = true;
+    }
+    else if(MatchChannelMap(chanmap, QuadMap))
+        mChannels = DevFmtQuad;
+    else if(MatchChannelMap(chanmap, StereoMap))
+        mChannels = DevFmtStereo;
+    else
+        mChannels = DevFmtMono;
+    TRACE("Device ID %u got %zu position%s for %s%s\n", mId, chanmap.size(),
+        (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":"");
+}
+
+void DeviceNode::parseChannelCount(const spa_pod *value) noexcept
+{
+    /* As a fallback with just a channel count, just assume mono or stereo. */
+    const auto chancount = get_value<SPA_TYPE_Int>(value);
+    if(!chancount) return;
+
+    mIs51Rear = false;
+
+    if(*chancount >= 2)
+        mChannels = DevFmtStereo;
+    else if(*chancount >= 1)
+        mChannels = DevFmtMono;
+    TRACE("Device ID %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s",
+        DevFmtChannelsString(mChannels));
+}
+
+
+constexpr char MonitorPrefix[]{"Monitor of "};
+constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1;
+constexpr char AudioSinkClass[]{"Audio/Sink"};
+constexpr char AudioSourceClass[]{"Audio/Source"};
+constexpr char AudioDuplexClass[]{"Audio/Duplex"};
+constexpr char StreamClass[]{"Stream/"};
+
+/* A generic PipeWire node proxy object used to track changes to sink and
+ * source nodes.
+ */
+struct NodeProxy {
+    static constexpr pw_node_events CreateNodeEvents()
+    {
+        pw_node_events ret{};
+        ret.version = PW_VERSION_NODE_EVENTS;
+        ret.info = &NodeProxy::infoCallbackC;
+        ret.param = &NodeProxy::paramCallbackC;
+        return ret;
+    }
+
+    uint32_t mId{};
+
+    PwNodePtr mNode{};
+    spa_hook mListener{};
+
+    NodeProxy(uint32_t id, PwNodePtr node)
+      : mId{id}, mNode{std::move(node)}
+    {
+        static constexpr pw_node_events nodeEvents{CreateNodeEvents()};
+        ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this);
+
+        /* Track changes to the enumerable formats (indicates the default
+         * format, which is what we're interested in).
+         */
+        uint32_t fmtids[]{SPA_PARAM_EnumFormat};
+        ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids));
+    }
+    ~NodeProxy()
+    { spa_hook_remove(&mListener); }
+
+
+    void infoCallback(const pw_node_info *info);
+    static void infoCallbackC(void *object, const pw_node_info *info)
+    { static_cast<NodeProxy*>(object)->infoCallback(info); }
+
+    void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param);
+    static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next,
+        const spa_pod *param)
+    { static_cast<NodeProxy*>(object)->paramCallback(seq, id, index, next, param); }
+};
+
+void NodeProxy::infoCallback(const pw_node_info *info)
+{
+    /* We only care about property changes here (media class, name/desc).
+     * Format changes will automatically invoke the param callback.
+     *
+     * TODO: Can the media class or name/desc change without being removed and
+     * readded?
+     */
+    if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS))
+    {
+        /* Can this actually change? */
+        const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)};
+        if(unlikely(!media_class)) return;
+
+        NodeType ntype{};
+        if(al::strcasecmp(media_class, AudioSinkClass) == 0)
+            ntype = NodeType::Sink;
+        else if(al::strcasecmp(media_class, AudioSourceClass) == 0)
+            ntype = NodeType::Source;
+        else if(al::strcasecmp(media_class, AudioDuplexClass) == 0)
+            ntype = NodeType::Duplex;
+        else
+        {
+            TRACE("Dropping device node %u which became type \"%s\"\n", info->id, media_class);
+            DeviceNode::Remove(info->id);
+            return;
+        }
+
+        const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)};
+        const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)};
+        if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK);
+        if(!nodeName || !*nodeName) nodeName = devName;
+
+        const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)};
+        TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)",
+            form_factor?" (":"", form_factor?form_factor:"", form_factor?")":"");
+        TRACE("  \"%s\" = ID %u\n", nodeName ? nodeName : "(nil)", info->id);
+
+        DeviceNode &node = DeviceNode::Add(info->id);
+        if(nodeName && *nodeName) node.mName = nodeName;
+        else node.mName = "PipeWire node #"+std::to_string(info->id);
+        node.mDevName = devName ? devName : "";
+        node.mType = ntype;
+        node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0
+            || al::strcasecmp(form_factor, "headset") == 0);
+    }
+}
+
+void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param)
+{
+    if(id == SPA_PARAM_EnumFormat)
+    {
+        DeviceNode *node{DeviceNode::Find(mId)};
+        if(unlikely(!node)) return;
+
+        if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)})
+            node->parseSampleRate(&prop->value);
+
+        if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)})
+            node->parsePositions(&prop->value);
+        else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr)
+            node->parseChannelCount(&prop->value);
+    }
+}
+
+
+/* A metadata proxy object used to query the default sink and source. */
+struct MetadataProxy {
+    static constexpr pw_metadata_events CreateMetadataEvents()
+    {
+        pw_metadata_events ret{};
+        ret.version = PW_VERSION_METADATA_EVENTS;
+        ret.property = &MetadataProxy::propertyCallbackC;
+        return ret;
+    }
+
+    uint32_t mId{};
+
+    PwMetadataPtr mMetadata{};
+    spa_hook mListener{};
+
+    MetadataProxy(uint32_t id, PwMetadataPtr mdata)
+      : mId{id}, mMetadata{std::move(mdata)}
+    {
+        static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()};
+        ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this);
+    }
+    ~MetadataProxy()
+    { spa_hook_remove(&mListener); }
+
+
+    int propertyCallback(uint32_t id, const char *key, const char *type, const char *value);
+    static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type,
+        const char *value)
+    { return static_cast<MetadataProxy*>(object)->propertyCallback(id, key, type, value); }
+};
+
+int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type,
+    const char *value)
+{
+    if(id != PW_ID_CORE)
+        return 0;
+
+    bool isCapture{};
+    if(std::strcmp(key, "default.audio.sink") == 0)
+        isCapture = false;
+    else if(std::strcmp(key, "default.audio.source") == 0)
+        isCapture = true;
+    else
+        return 0;
+
+    if(!type)
+    {
+        TRACE("Default %s device cleared\n", isCapture ? "capture" : "playback");
+        if(!isCapture) DefaultSinkDevice.clear();
+        else DefaultSourceDevice.clear();
+        return 0;
+    }
+    if(std::strcmp(type, "Spa:String:JSON") != 0)
+    {
+        ERR("Unexpected %s property type: %s\n", key, type);
+        return 0;
+    }
+
+    spa_json it[2]{};
+    spa_json_init(&it[0], value, strlen(value));
+    if(spa_json_enter_object(&it[0], &it[1]) <= 0)
+        return 0;
+
+    auto get_json_string = [](spa_json *iter)
+    {
+        al::optional<std::string> str;
+
+        const char *val{};
+        int len{spa_json_next(iter, &val)};
+        if(len <= 0) return str;
+
+        str.emplace().resize(static_cast<uint>(len), '\0');
+        if(spa_json_parse_string(val, len, &str->front()) <= 0)
+            str.reset();
+        else while(!str->empty() && str->back() == '\0')
+            str->pop_back();
+        return str;
+    };
+    while(auto propKey = get_json_string(&it[1]))
+    {
+        if(*propKey == "name")
+        {
+            auto propValue = get_json_string(&it[1]);
+            if(!propValue) break;
+
+            TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback",
+                propValue->c_str());
+            if(!isCapture)
+                DefaultSinkDevice = std::move(*propValue);
+            else
+                DefaultSourceDevice = std::move(*propValue);
+        }
+        else
+        {
+            const char *v{};
+            if(spa_json_next(&it[1], &v) <= 0)
+                break;
+        }
+    }
+    return 0;
+}
+
+
+bool EventManager::init()
+{
+    mLoop = ThreadMainloop::Create("PWEventThread");
+    if(!mLoop)
+    {
+        ERR("Failed to create PipeWire event thread loop (errno: %d)\n", errno);
+        return false;
+    }
+
+    mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr));
+    if(!mContext)
+    {
+        ERR("Failed to create PipeWire event context (errno: %d)\n", errno);
+        return false;
+    }
+
+    mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
+    if(!mCore)
+    {
+        ERR("Failed to connect PipeWire event context (errno: %d)\n", errno);
+        return false;
+    }
+
+    mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)};
+    if(!mRegistry)
+    {
+        ERR("Failed to get PipeWire event registry (errno: %d)\n", errno);
+        return false;
+    }
+
+    static constexpr pw_core_events coreEvents{CreateCoreEvents()};
+    static constexpr pw_registry_events registryEvents{CreateRegistryEvents()};
+
+    ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this);
+    ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, &registryEvents, this);
+
+    /* Set an initial sequence ID for initialization, to trigger after the
+     * registry is first populated.
+     */
+    mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0);
+
+    if(int res{mLoop.start()})
+    {
+        ERR("Failed to start PipeWire event thread loop (res: %d)\n", res);
+        return false;
+    }
+
+    return true;
+}
+
+EventManager::~EventManager()
+{
+    if(mLoop) mLoop.stop();
+
+    for(NodeProxy *node : mNodeList)
+        al::destroy_at(node);
+    if(mDefaultMetadata)
+        al::destroy_at(mDefaultMetadata);
+}
+
+void EventManager::kill()
+{
+    if(mLoop) mLoop.stop();
+
+    for(NodeProxy *node : mNodeList)
+        al::destroy_at(node);
+    mNodeList.clear();
+    if(mDefaultMetadata)
+        al::destroy_at(mDefaultMetadata);
+    mDefaultMetadata = nullptr;
+
+    mRegistry = nullptr;
+    mCore = nullptr;
+    mContext = nullptr;
+    mLoop = nullptr;
+}
+
+void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version,
+    const spa_dict *props)
+{
+    /* We're only interested in interface nodes. */
+    if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
+    {
+        const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)};
+        if(!media_class) return;
+
+        /* Specifically, audio sinks and sources (and duplexes). */
+        const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0
+            || al::strcasecmp(media_class, AudioSourceClass) == 0
+            || al::strcasecmp(media_class, AudioDuplexClass) == 0};
+        if(!isGood)
+        {
+            if(std::strstr(media_class, "/Video") == nullptr
+                && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0)
+                TRACE("Ignoring node class %s\n", media_class);
+            return;
+        }
+
+        /* Create the proxy object. */
+        auto node = PwNodePtr{static_cast<pw_node*>(pw_registry_bind(mRegistry.get(), id, type,
+            version, sizeof(NodeProxy)))};
+        if(!node)
+        {
+            ERR("Failed to create node proxy object (errno: %d)\n", errno);
+            return;
+        }
+
+        /* Initialize the NodeProxy to hold the node object, add it to the
+         * active node list, and update the sync point.
+         */
+        auto *proxy = static_cast<NodeProxy*>(pw_proxy_get_user_data(as<pw_proxy*>(node.get())));
+        mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node)));
+        syncInit();
+
+        /* Signal any waiters that we have found a source or sink for audio
+         * support.
+         */
+        if(!mHasAudio.exchange(true, std::memory_order_acq_rel))
+            mLoop.signal(false);
+    }
+    else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0)
+    {
+        const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)};
+        if(!data_class) return;
+
+        if(std::strcmp(data_class, "default") != 0)
+        {
+            TRACE("Ignoring metadata \"%s\"\n", data_class);
+            return;
+        }
+
+        if(mDefaultMetadata)
+        {
+            ERR("Duplicate default metadata\n");
+            return;
+        }
+
+        auto mdata = PwMetadataPtr{static_cast<pw_metadata*>(pw_registry_bind(mRegistry.get(), id,
+            type, version, sizeof(MetadataProxy)))};
+        if(!mdata)
+        {
+            ERR("Failed to create metadata proxy object (errno: %d)\n", errno);
+            return;
+        }
+
+        auto *proxy = static_cast<MetadataProxy*>(
+            pw_proxy_get_user_data(as<pw_proxy*>(mdata.get())));
+        mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata));
+        syncInit();
+    }
+}
+
+void EventManager::removeCallback(uint32_t id)
+{
+    DeviceNode::Remove(id);
+
+    auto clear_node = [id](NodeProxy *node) noexcept
+    {
+        if(node->mId != id)
+            return false;
+        al::destroy_at(node);
+        return true;
+    };
+    auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node);
+    mNodeList.erase(node_end, mNodeList.end());
+
+    if(mDefaultMetadata && mDefaultMetadata->mId == id)
+    {
+        al::destroy_at(mDefaultMetadata);
+        mDefaultMetadata = nullptr;
+    }
+}
+
+void EventManager::coreCallback(uint32_t id, int seq)
+{
+    if(id == PW_ID_CORE && seq == mInitSeq)
+    {
+        /* Initialization done. Remove this callback and signal anyone that may
+         * be waiting.
+         */
+        spa_hook_remove(&mCoreListener);
+
+        mInitDone.store(true);
+        mLoop.signal(false);
+    }
+}
+
+
+enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true };
+spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p)
+{
+    spa_audio_info_raw info{};
+    if(use_f32p)
+    {
+        device->FmtType = DevFmtFloat;
+        info.format = SPA_AUDIO_FORMAT_F32P;
+    }
+    else switch(device->FmtType)
+    {
+    case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break;
+    case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break;
+    case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break;
+    case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break;
+    case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break;
+    case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break;
+    case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break;
+    }
+
+    info.rate = device->Frequency;
+
+    al::span<const spa_audio_channel> map{};
+    switch(device->FmtChans)
+    {
+    case DevFmtMono: map = MonoMap; break;
+    case DevFmtStereo: map = StereoMap; break;
+    case DevFmtQuad: map = QuadMap; break;
+    case DevFmtX51:
+        if(is51rear) map = X51RearMap;
+        else map = X51Map;
+        break;
+    case DevFmtX61: map = X61Map; break;
+    case DevFmtX71: map = X71Map; break;
+    case DevFmtAmbi3D:
+        info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED;
+        info.channels = device->channelsFromFmt();
+        break;
+    }
+    if(!map.empty())
+    {
+        info.channels = static_cast<uint32_t>(map.size());
+        std::copy(map.begin(), map.end(), info.position);
+    }
+
+    return info;
+}
+
+class PipeWirePlayback final : public BackendBase {
+    void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
+    static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
+        const char *error)
+    { static_cast<PipeWirePlayback*>(data)->stateChangedCallback(old, state, error); }
+
+    void ioChangedCallback(uint32_t id, void *area, uint32_t size);
+    static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size)
+    { static_cast<PipeWirePlayback*>(data)->ioChangedCallback(id, area, size); }
+
+    void outputCallback();
+    static void outputCallbackC(void *data)
+    { static_cast<PipeWirePlayback*>(data)->outputCallback(); }
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+    ClockLatency getClockLatency() override;
+
+    uint32_t mTargetId{PwIdAny};
+    nanoseconds mTimeBase{0};
+    ThreadMainloop mLoop;
+    PwContextPtr mContext;
+    PwCorePtr mCore;
+    PwStreamPtr mStream;
+    spa_hook mStreamListener{};
+    spa_io_rate_match *mRateMatch{};
+    std::unique_ptr<float*[]> mChannelPtrs;
+    uint mNumChannels{};
+
+    static constexpr pw_stream_events CreateEvents()
+    {
+        pw_stream_events ret{};
+        ret.version = PW_VERSION_STREAM_EVENTS;
+        ret.state_changed = &PipeWirePlayback::stateChangedCallbackC;
+        ret.io_changed = &PipeWirePlayback::ioChangedCallbackC;
+        ret.process = &PipeWirePlayback::outputCallbackC;
+        return ret;
+    }
+
+public:
+    PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
+    ~PipeWirePlayback()
+    {
+        /* Stop the mainloop so the stream can be properly destroyed. */
+        if(mLoop) mLoop.stop();
+    }
+
+    DEF_NEWDEL(PipeWirePlayback)
+};
+
+
+void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
+{ mLoop.signal(false); }
+
+void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size)
+{
+    switch(id)
+    {
+    case SPA_IO_RateMatch:
+        if(size >= sizeof(spa_io_rate_match))
+            mRateMatch = static_cast<spa_io_rate_match*>(area);
+        break;
+    }
+}
+
+void PipeWirePlayback::outputCallback()
+{
+    pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
+    if(unlikely(!pw_buf)) return;
+
+    /* For planar formats, each datas[] seems to contain one channel, so store
+     * the pointers in an array. Limit the render length in case the available
+     * buffer length in any one channel is smaller than we wanted (shouldn't
+     * be, but just in case).
+     */
+    spa_data *datas{pw_buf->buffer->datas};
+    const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)};
+    /* TODO: How many samples should actually be written? 'maxsize' can be 16k
+     * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains
+     * a 'size' field that apparently indicates how many samples should be
+     * written per update, but it's not obviously right.
+     */
+    uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize};
+    for(size_t i{0};i < chancount;++i)
+    {
+        length = minu(length, datas[i].maxsize/sizeof(float));
+        mChannelPtrs[i] = static_cast<float*>(datas[i].data);
+    }
+
+    mDevice->renderSamples({mChannelPtrs.get(), chancount}, length);
+
+    for(size_t i{0};i < chancount;++i)
+    {
+        datas[i].chunk->offset = 0;
+        datas[i].chunk->stride = sizeof(float);
+        datas[i].chunk->size   = length * sizeof(float);
+    }
+    pw_buf->size = length;
+    pw_stream_queue_buffer(mStream.get(), pw_buf);
+}
+
+
+void PipeWirePlayback::open(const char *name)
+{
+    static std::atomic<uint> OpenCount{0};
+
+    uint32_t targetid{PwIdAny};
+    std::string devname{};
+    gEventHandler.waitForInit();
+    if(!name)
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match = devlist.cend();
+        if(!DefaultSinkDevice.empty())
+        {
+            auto match_default = [](const DeviceNode &n) -> bool
+            { return n.mDevName == DefaultSinkDevice; };
+            match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
+        }
+        if(match == devlist.cend())
+        {
+            auto match_playback = [](const DeviceNode &n) -> bool
+            { return n.mType != NodeType::Source; };
+            match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback);
+            if(match == devlist.cend())
+                throw al::backend_exception{al::backend_error::NoDevice,
+                    "No PipeWire playback device found"};
+        }
+
+        targetid = match->mId;
+        devname = match->mName;
+    }
+    else
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match_name = [name](const DeviceNode &n) -> bool
+        { return n.mType != NodeType::Source && n.mName == name; };
+        auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
+        if(match == devlist.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
+
+        targetid = match->mId;
+        devname = match->mName;
+    }
+
+    if(!mLoop)
+    {
+        const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
+        const std::string thread_name{"ALSoftP" + std::to_string(count)};
+        mLoop = ThreadMainloop::Create(thread_name.c_str());
+        if(!mLoop)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to create PipeWire mainloop (errno: %d)", errno};
+        if(int res{mLoop.start()})
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to start PipeWire mainloop (res: %d)", res};
+    }
+    MainloopUniqueLock mlock{mLoop};
+    if(!mContext)
+    {
+        pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
+        mContext = mLoop.newContext(cprops);
+        if(!mContext)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to create PipeWire event context (errno: %d)\n", errno};
+    }
+    if(!mCore)
+    {
+        mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
+        if(!mCore)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to connect PipeWire event context (errno: %d)\n", errno};
+    }
+    mlock.unlock();
+
+    /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
+
+    mTargetId = targetid;
+    if(!devname.empty())
+        mDevice->DeviceName = std::move(devname);
+    else
+        mDevice->DeviceName = pwireDevice;
+}
+
+bool PipeWirePlayback::reset()
+{
+    if(mStream)
+    {
+        MainloopLockGuard _{mLoop};
+        mStream = nullptr;
+    }
+    mStreamListener = {};
+    mRateMatch = nullptr;
+    mTimeBase = GetDeviceClockTime(mDevice);
+
+    /* If connecting to a specific device, update various device parameters to
+     * match its format.
+     */
+    bool is51rear{false};
+    mDevice->Flags.reset(DirectEar);
+    if(mTargetId != PwIdAny)
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
+        { return targetid == n.mId; };
+        auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
+        if(match != devlist.cend())
+        {
+            if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0)
+            {
+                /* Scale the update size if the sample rate changes. */
+                const double scale{static_cast<double>(match->mSampleRate) / mDevice->Frequency};
+                mDevice->Frequency = match->mSampleRate;
+                mDevice->UpdateSize = static_cast<uint>(clampd(mDevice->UpdateSize*scale + 0.5,
+                    64.0, 8192.0));
+                mDevice->BufferSize = mDevice->UpdateSize * 2;
+            }
+            if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig)
+                mDevice->FmtChans = match->mChannels;
+            if(match->mChannels == DevFmtStereo && match->mIsHeadphones)
+                mDevice->Flags.set(DirectEar);
+            is51rear = match->mIs51Rear;
+        }
+    }
+    /* Force planar 32-bit float output for playback. This is what PipeWire
+     * handles internally, and it's easier for us too.
+     */
+    spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)};
+
+    /* TODO: How to tell what an appropriate size is? Examples just use this
+     * magic value.
+     */
+    constexpr uint32_t pod_buffer_size{1024};
+    auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
+    spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
+
+    const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
+    if(!params)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to set PipeWire audio format parameters"};
+
+    pw_properties *props{pw_properties_new(
+        PW_KEY_MEDIA_TYPE, "Audio",
+        PW_KEY_MEDIA_CATEGORY, "Playback",
+        PW_KEY_MEDIA_ROLE, "Game",
+        PW_KEY_NODE_ALWAYS_PROCESS, "true",
+        nullptr)};
+    if(!props)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to create PipeWire stream properties (errno: %d)", errno};
+
+    auto&& binary = GetProcBinary();
+    const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
+    /* TODO: Which properties are actually needed here? Any others that could
+     * be useful?
+     */
+    pw_properties_set(props, PW_KEY_NODE_NAME, appname);
+    pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
+    pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize,
+        mDevice->Frequency);
+    pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
+
+    MainloopUniqueLock plock{mLoop};
+    /* The stream takes overship of 'props', even in the case of failure. */
+    mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)};
+    if(!mStream)
+        throw al::backend_exception{al::backend_error::NoDevice,
+            "Failed to create PipeWire stream (errno: %d)", errno};
+    static constexpr pw_stream_events streamEvents{CreateEvents()};
+    pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
+
+    constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
+        | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
+    if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, &params, 1)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Error connecting PipeWire stream (res: %d)", res};
+
+    /* Wait for the stream to become paused (ready to start streaming). */
+    pw_stream_state state{};
+    const char *error{};
+    plock.wait([stream=mStream.get(),&state,&error]()
+    {
+        state = pw_stream_get_state(stream, &error);
+        if(state == PW_STREAM_STATE_ERROR)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Error connecting PipeWire stream: \"%s\"", error};
+        return state == PW_STREAM_STATE_PAUSED;
+    });
+
+    /* TODO: Update mDevice->BufferSize with the total known buffering delay
+     * from the head of this playback stream to the tail of the device output.
+     */
+    mDevice->BufferSize = mDevice->UpdateSize * 2;
+    plock.unlock();
+
+    mNumChannels = mDevice->channelsFromFmt();
+    mChannelPtrs = std::make_unique<float*[]>(mNumChannels);
+
+    setDefaultWFXChannelOrder();
+
+    return true;
+}
+
+void PipeWirePlayback::start()
+{
+    MainloopUniqueLock plock{mLoop};
+    if(int res{pw_stream_set_active(mStream.get(), true)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start PipeWire stream (res: %d)", res};
+
+    /* Wait for the stream to start playing (would be nice to not, but we need
+     * the actual update size which is only available after starting).
+     */
+    pw_stream_state state{};
+    const char *error{};
+    plock.wait([stream=mStream.get(),&state,&error]()
+    {
+        state = pw_stream_get_state(stream, &error);
+        return state != PW_STREAM_STATE_PAUSED;
+    });
+
+    if(state == PW_STREAM_STATE_ERROR)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "PipeWire stream error: %s", error ? error : "(unknown)"};
+    if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size)
+    {
+        mDevice->UpdateSize = mRateMatch->size;
+        mDevice->BufferSize = mDevice->UpdateSize * 2;
+    }
+}
+
+void PipeWirePlayback::stop()
+{
+    MainloopUniqueLock plock{mLoop};
+    if(int res{pw_stream_set_active(mStream.get(), false)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to stop PipeWire stream (res: %d)", res};
+
+    /* Wait for the stream to stop playing. */
+    plock.wait([stream=mStream.get()]()
+    { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
+}
+
+ClockLatency PipeWirePlayback::getClockLatency()
+{
+    /* Given a real-time low-latency output, this is rather complicated to get
+     * accurate timing. So, here we go.
+     */
+
+    /* First, get the stream time info (tick delay, ticks played, and the
+     * CLOCK_MONOTONIC time closest to when that last tick was played).
+     */
+    pw_time ptime{};
+    if(mStream)
+    {
+        MainloopLockGuard _{mLoop};
+        if(int res{pw_stream_get_time(mStream.get(), &ptime)})
+            ERR("Failed to get PipeWire stream time (res: %d)\n", res);
+    }
+
+    /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
+     * monotonic clock closest to 'now', and the last mixer time at 'now').
+     */
+    nanoseconds mixtime{};
+    timespec tspec{};
+    uint refcount;
+    do {
+        refcount = mDevice->waitForMix();
+        mixtime = GetDeviceClockTime(mDevice);
+        clock_gettime(CLOCK_MONOTONIC, &tspec);
+        std::atomic_thread_fence(std::memory_order_acquire);
+    } while(refcount != ReadRef(mDevice->MixCount));
+
+    /* Convert the monotonic clock, stream ticks, and stream delay to
+     * nanoseconds.
+     */
+    nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
+    nanoseconds curtic{}, delay{};
+    if(unlikely(ptime.rate.denom < 1))
+    {
+        /* If there's no stream rate, the stream hasn't had a chance to get
+         * going and return time info yet. Just use dummy values.
+         */
+        ptime.now = monoclock.count();
+        curtic = mixtime;
+        delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
+    }
+    else
+    {
+        /* The stream gets recreated with each reset, so include the time that
+         * had already passed with previous streams.
+         */
+        curtic = mTimeBase;
+        /* More safely scale the ticks to avoid overflowing the pre-division
+         * temporary as it gets larger.
+         */
+        curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
+        curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
+            ptime.rate.denom;
+
+        /* The delay should be small enough to not worry about overflow. */
+        delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
+    }
+
+    /* If the mixer time is ahead of the stream time, there's that much more
+     * delay relative to the stream delay.
+     */
+    if(mixtime > curtic)
+        delay += mixtime - curtic;
+    /* Reduce the delay according to how much time has passed since the known
+     * stream time. This isn't 100% accurate since the system monotonic clock
+     * doesn't tick at the exact same rate as the audio device, but it should
+     * be good enough with ptime.now being constantly updated every few
+     * milliseconds with ptime.ticks.
+     */
+    delay -= monoclock - nanoseconds{ptime.now};
+
+    /* Return the mixer time and delay. Clamp the delay to no less than 0,
+     * incase timer drift got that severe.
+     */
+    ClockLatency ret{};
+    ret.ClockTime = mixtime;
+    ret.Latency = std::max(delay, nanoseconds{});
+
+    return ret;
+}
+
+
+class PipeWireCapture final : public BackendBase {
+    void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error);
+    static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state,
+        const char *error)
+    { static_cast<PipeWireCapture*>(data)->stateChangedCallback(old, state, error); }
+
+    void inputCallback();
+    static void inputCallbackC(void *data)
+    { static_cast<PipeWireCapture*>(data)->inputCallback(); }
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    uint32_t mTargetId{PwIdAny};
+    ThreadMainloop mLoop;
+    PwContextPtr mContext;
+    PwCorePtr mCore;
+    PwStreamPtr mStream;
+    spa_hook mStreamListener{};
+
+    RingBufferPtr mRing{};
+
+    static constexpr pw_stream_events CreateEvents()
+    {
+        pw_stream_events ret{};
+        ret.version = PW_VERSION_STREAM_EVENTS;
+        ret.state_changed = &PipeWireCapture::stateChangedCallbackC;
+        ret.process = &PipeWireCapture::inputCallbackC;
+        return ret;
+    }
+
+public:
+    PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { }
+    ~PipeWireCapture() { if(mLoop) mLoop.stop(); }
+
+    DEF_NEWDEL(PipeWireCapture)
+};
+
+
+void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*)
+{ mLoop.signal(false); }
+
+void PipeWireCapture::inputCallback()
+{
+    pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())};
+    if(unlikely(!pw_buf)) return;
+
+    spa_data *bufdata{pw_buf->buffer->datas};
+    const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)};
+    const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)};
+
+    mRing->write(static_cast<char*>(bufdata->data) + offset, size / mRing->getElemSize());
+
+    pw_stream_queue_buffer(mStream.get(), pw_buf);
+}
+
+
+void PipeWireCapture::open(const char *name)
+{
+    static std::atomic<uint> OpenCount{0};
+
+    uint32_t targetid{PwIdAny};
+    std::string devname{};
+    gEventHandler.waitForInit();
+    if(!name)
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match = devlist.cend();
+        if(!DefaultSourceDevice.empty())
+        {
+            auto match_default = [](const DeviceNode &n) -> bool
+            { return n.mDevName == DefaultSourceDevice; };
+            match = std::find_if(devlist.cbegin(), devlist.cend(), match_default);
+        }
+        if(match == devlist.cend())
+        {
+            auto match_capture = [](const DeviceNode &n) -> bool
+            { return n.mType != NodeType::Sink; };
+            match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture);
+        }
+        if(match == devlist.cend())
+        {
+            match = devlist.cbegin();
+            if(match == devlist.cend())
+                throw al::backend_exception{al::backend_error::NoDevice,
+                    "No PipeWire capture device found"};
+        }
+
+        targetid = match->mId;
+        if(match->mType != NodeType::Sink) devname = match->mName;
+        else devname = MonitorPrefix+match->mName;
+    }
+    else
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match_name = [name](const DeviceNode &n) -> bool
+        { return n.mType != NodeType::Sink && n.mName == name; };
+        auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name);
+        if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0)
+        {
+            const char *sinkname{name + MonitorPrefixLen};
+            auto match_sinkname = [sinkname](const DeviceNode &n) -> bool
+            { return n.mType == NodeType::Sink && n.mName == sinkname; };
+            match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname);
+        }
+        if(match == devlist.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
+
+        targetid = match->mId;
+        devname = name;
+    }
+
+    if(!mLoop)
+    {
+        const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)};
+        const std::string thread_name{"ALSoftC" + std::to_string(count)};
+        mLoop = ThreadMainloop::Create(thread_name.c_str());
+        if(!mLoop)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to create PipeWire mainloop (errno: %d)", errno};
+        if(int res{mLoop.start()})
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to start PipeWire mainloop (res: %d)", res};
+    }
+    MainloopUniqueLock mlock{mLoop};
+    if(!mContext)
+    {
+        pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)};
+        mContext = mLoop.newContext(cprops);
+        if(!mContext)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to create PipeWire event context (errno: %d)\n", errno};
+    }
+    if(!mCore)
+    {
+        mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)};
+        if(!mCore)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Failed to connect PipeWire event context (errno: %d)\n", errno};
+    }
+    mlock.unlock();
+
+    /* TODO: Ensure the target ID is still valid/usable and accepts streams. */
+
+    mTargetId = targetid;
+    if(!devname.empty())
+        mDevice->DeviceName = std::move(devname);
+    else
+        mDevice->DeviceName = pwireInput;
+
+
+    bool is51rear{false};
+    if(mTargetId != PwIdAny)
+    {
+        EventWatcherLockGuard _{gEventHandler};
+        auto&& devlist = DeviceNode::GetList();
+
+        auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool
+        { return targetid == n.mId; };
+        auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id);
+        if(match != devlist.cend())
+            is51rear = match->mIs51Rear;
+    }
+    spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)};
+
+    constexpr uint32_t pod_buffer_size{1024};
+    auto pod_buffer = std::make_unique<al::byte[]>(pod_buffer_size);
+    spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)};
+
+    const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)};
+    if(!params[0])
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to set PipeWire audio format parameters"};
+
+    pw_properties *props{pw_properties_new(
+        PW_KEY_MEDIA_TYPE, "Audio",
+        PW_KEY_MEDIA_CATEGORY, "Capture",
+        PW_KEY_MEDIA_ROLE, "Game",
+        PW_KEY_NODE_ALWAYS_PROCESS, "true",
+        nullptr)};
+    if(!props)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to create PipeWire stream properties (errno: %d)", errno};
+
+    auto&& binary = GetProcBinary();
+    const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"};
+    pw_properties_set(props, PW_KEY_NODE_NAME, appname);
+    pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname);
+    /* We don't actually care what the latency/update size is, as long as it's
+     * reasonable. Unfortunately, when unspecified PipeWire seems to default to
+     * around 40ms, which isn't great. So request 20ms instead.
+     */
+    pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50,
+        mDevice->Frequency);
+    pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency);
+
+    MainloopUniqueLock plock{mLoop};
+    mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)};
+    if(!mStream)
+        throw al::backend_exception{al::backend_error::NoDevice,
+            "Failed to create PipeWire stream (errno: %d)", errno};
+    static constexpr pw_stream_events streamEvents{CreateEvents()};
+    pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this);
+
+    constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE
+        | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS};
+    if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, mTargetId, Flags, params, 1)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Error connecting PipeWire stream (res: %d)", res};
+
+    /* Wait for the stream to become paused (ready to start streaming). */
+    pw_stream_state state{};
+    const char *error{};
+    plock.wait([stream=mStream.get(),&state,&error]()
+    {
+        state = pw_stream_get_state(stream, &error);
+        if(state == PW_STREAM_STATE_ERROR)
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Error connecting PipeWire stream: \"%s\"", error};
+        return state == PW_STREAM_STATE_PAUSED;
+    });
+    plock.unlock();
+
+    setDefaultWFXChannelOrder();
+
+    /* Ensure at least a 100ms capture buffer. */
+    mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize),
+        mDevice->frameSizeFromFmt(), false);
+}
+
+
+void PipeWireCapture::start()
+{
+    MainloopUniqueLock plock{mLoop};
+    if(int res{pw_stream_set_active(mStream.get(), true)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start PipeWire stream (res: %d)", res};
+
+    pw_stream_state state{};
+    const char *error{};
+    plock.wait([stream=mStream.get(),&state,&error]()
+    {
+        state = pw_stream_get_state(stream, &error);
+        return state != PW_STREAM_STATE_PAUSED;
+    });
+
+    if(state == PW_STREAM_STATE_ERROR)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "PipeWire stream error: %s", error ? error : "(unknown)"};
+}
+
+void PipeWireCapture::stop()
+{
+    MainloopUniqueLock plock{mLoop};
+    if(int res{pw_stream_set_active(mStream.get(), false)})
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to stop PipeWire stream (res: %d)", res};
+
+    plock.wait([stream=mStream.get()]()
+    { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; });
+}
+
+uint PipeWireCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+void PipeWireCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+} // namespace
+
+
+bool PipeWireBackendFactory::init()
+{
+    if(!pwire_load())
+        return false;
+
+    pw_init(0, nullptr);
+    if(!gEventHandler.init())
+        return false;
+
+    if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false)
+        && !gEventHandler.waitForAudio())
+    {
+        gEventHandler.kill();
+        /* TODO: Temporary warning, until PipeWire gets a proper way to report
+         * audio support.
+         */
+        WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong.\n");
+        return false;
+    }
+    return true;
+}
+
+bool PipeWireBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string PipeWireBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+
+    gEventHandler.waitForInit();
+    EventWatcherLockGuard _{gEventHandler};
+    auto&& devlist = DeviceNode::GetList();
+
+    auto match_defsink = [](const DeviceNode &n) -> bool
+    { return n.mDevName == DefaultSinkDevice; };
+    auto match_defsource = [](const DeviceNode &n) -> bool
+    { return n.mDevName == DefaultSourceDevice; };
+
+    auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool
+    { return lhs.mId < rhs.mId; };
+    std::sort(devlist.begin(), devlist.end(), sort_devnode);
+
+    auto defmatch = devlist.cbegin();
+    switch(type)
+    {
+    case BackendType::Playback:
+        defmatch = std::find_if(defmatch, devlist.cend(), match_defsink);
+        if(defmatch != devlist.cend())
+        {
+            /* Includes null char. */
+            outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
+        }
+        for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
+        {
+            if(iter != defmatch && iter->mType != NodeType::Source)
+                outnames.append(iter->mName.c_str(), iter->mName.length()+1);
+        }
+        break;
+    case BackendType::Capture:
+        defmatch = std::find_if(defmatch, devlist.cend(), match_defsource);
+        if(defmatch != devlist.cend())
+        {
+            if(defmatch->mType == NodeType::Sink)
+                outnames.append(MonitorPrefix);
+            outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1);
+        }
+        for(auto iter = devlist.cbegin();iter != devlist.cend();++iter)
+        {
+            if(iter != defmatch)
+            {
+                if(iter->mType == NodeType::Sink)
+                    outnames.append(MonitorPrefix);
+                outnames.append(iter->mName.c_str(), iter->mName.length()+1);
+            }
+        }
+        break;
+    }
+
+    return outnames;
+}
+
+BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new PipeWirePlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new PipeWireCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &PipeWireBackendFactory::getFactory()
+{
+    static PipeWireBackendFactory factory{};
+    return factory;
+}

+ 23 - 0
libs/openal-soft/Alc/backends/pipewire.h

@@ -0,0 +1,23 @@
+#ifndef BACKENDS_PIPEWIRE_H
+#define BACKENDS_PIPEWIRE_H
+
+#include <string>
+
+#include "base.h"
+
+struct DeviceBase;
+
+struct PipeWireBackendFactory final : public BackendFactory {
+public:
+    bool init() override;
+
+    bool querySupport(BackendType type) override;
+
+    std::string probe(BackendType type) override;
+
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
+
+    static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_PIPEWIRE_H */

+ 28 - 23
libs/openal-soft/Alc/backends/portaudio.cpp

@@ -20,15 +20,15 @@
 
 #include "config.h"
 
-#include "backends/portaudio.h"
+#include "portaudio.h"
 
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 
-#include "alcmain.h"
-#include "alu.h"
-#include "alconfig.h"
+#include "alc/alconfig.h"
+#include "alnumeric.h"
+#include "core/device.h"
 #include "core/logging.h"
 #include "dynload.h"
 #include "ringbuffer.h"
@@ -72,7 +72,7 @@ MAKE_FUNC(Pa_GetStreamInfo);
 
 
 struct PortPlayback final : public BackendBase {
-    PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~PortPlayback() override;
 
     int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@@ -123,53 +123,58 @@ void PortPlayback::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
-    mUpdateSize = mDevice->UpdateSize;
-
+    PaStreamParameters params{};
     auto devidopt = ConfigValueInt(nullptr, "port", "device");
-    if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
-    else mParams.device = Pa_GetDefaultOutputDevice();
-    mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
-    mParams.hostApiSpecificStreamInfo = nullptr;
+    if(devidopt && *devidopt >= 0) params.device = *devidopt;
+    else params.device = Pa_GetDefaultOutputDevice();
+    params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
+    params.hostApiSpecificStreamInfo = nullptr;
 
-    mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+    params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
 
     switch(mDevice->FmtType)
     {
     case DevFmtByte:
-        mParams.sampleFormat = paInt8;
+        params.sampleFormat = paInt8;
         break;
     case DevFmtUByte:
-        mParams.sampleFormat = paUInt8;
+        params.sampleFormat = paUInt8;
         break;
     case DevFmtUShort:
         /* fall-through */
     case DevFmtShort:
-        mParams.sampleFormat = paInt16;
+        params.sampleFormat = paInt16;
         break;
     case DevFmtUInt:
         /* fall-through */
     case DevFmtInt:
-        mParams.sampleFormat = paInt32;
+        params.sampleFormat = paInt32;
         break;
     case DevFmtFloat:
-        mParams.sampleFormat = paFloat32;
+        params.sampleFormat = paFloat32;
         break;
     }
 
 retry_open:
-    PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
+    PaStream *stream{};
+    PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
         paNoFlag, &PortPlayback::writeCallbackC, this)};
     if(err != paNoError)
     {
-        if(mParams.sampleFormat == paFloat32)
+        if(params.sampleFormat == paFloat32)
         {
-            mParams.sampleFormat = paInt16;
+            params.sampleFormat = paInt16;
             goto retry_open;
         }
         throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
             Pa_GetErrorText(err)};
     }
 
+    Pa_CloseStream(mStream);
+    mStream = stream;
+    mParams = params;
+    mUpdateSize = mDevice->UpdateSize;
+
     mDevice->DeviceName = name;
 }
 
@@ -195,7 +200,7 @@ bool PortPlayback::reset()
         return false;
     }
 
-    if(mParams.channelCount == 2)
+    if(mParams.channelCount >= 2)
         mDevice->FmtChans = DevFmtStereo;
     else if(mParams.channelCount == 1)
         mDevice->FmtChans = DevFmtMono;
@@ -226,7 +231,7 @@ void PortPlayback::stop()
 
 
 struct PortCapture final : public BackendBase {
-    PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~PortCapture() override;
 
     int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
@@ -426,7 +431,7 @@ std::string PortBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new PortPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/portaudio.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_PORTAUDIO_H
 #define BACKENDS_PORTAUDIO_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct PortBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 78 - 131
libs/openal-soft/Alc/backends/pulseaudio.cpp

@@ -21,33 +21,49 @@
 
 #include "config.h"
 
-#include "backends/pulseaudio.h"
-
-#include <poll.h>
-#include <cstring>
+#include "pulseaudio.h"
 
+#include <algorithm>
 #include <array>
-#include <string>
-#include <vector>
 #include <atomic>
-#include <thread>
-#include <algorithm>
-#include <functional>
+#include <bitset>
+#include <chrono>
 #include <condition_variable>
-
-#include "alcmain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "compat.h"
+#include <cstring>
+#include <functional>
+#include <limits>
+#include <mutex>
+#include <new>
+#include <poll.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string>
+#include <sys/types.h>
+#include <thread>
+#include <utility>
+
+#include "albyte.h"
+#include "alc/alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "dynload.h"
+#include "opthelpers.h"
 #include "strutils.h"
+#include "vector.h"
 
 #include <pulse/pulseaudio.h>
 
 
 namespace {
 
+using uint = unsigned int;
+
 #ifdef HAVE_DYNLOAD
 #define PULSE_FUNCS(MAGIC)                                                    \
     MAGIC(pa_mainloop_new);                                                   \
@@ -220,78 +236,6 @@ constexpr pa_channel_map MonoChanMap{
     }
 };
 
-al::optional<Channel> ChannelFromPulse(pa_channel_position_t chan)
-{
-    switch(chan)
-    {
-    case PA_CHANNEL_POSITION_INVALID: break;
-    case PA_CHANNEL_POSITION_MONO: return al::make_optional(FrontCenter);
-    case PA_CHANNEL_POSITION_FRONT_LEFT: return al::make_optional(FrontLeft);
-    case PA_CHANNEL_POSITION_FRONT_RIGHT: return al::make_optional(FrontRight);
-    case PA_CHANNEL_POSITION_FRONT_CENTER: return al::make_optional(FrontCenter);
-    case PA_CHANNEL_POSITION_REAR_CENTER: return al::make_optional(BackCenter);
-    case PA_CHANNEL_POSITION_REAR_LEFT: return al::make_optional(BackLeft);
-    case PA_CHANNEL_POSITION_REAR_RIGHT: return al::make_optional(BackRight);
-    case PA_CHANNEL_POSITION_LFE: return al::make_optional(LFE);
-    case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: break;
-    case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: break;
-    case PA_CHANNEL_POSITION_SIDE_LEFT: return al::make_optional(SideLeft);
-    case PA_CHANNEL_POSITION_SIDE_RIGHT: return al::make_optional(SideRight);
-    case PA_CHANNEL_POSITION_AUX0: break;
-    case PA_CHANNEL_POSITION_AUX1: break;
-    case PA_CHANNEL_POSITION_AUX2: break;
-    case PA_CHANNEL_POSITION_AUX3: break;
-    case PA_CHANNEL_POSITION_AUX4: break;
-    case PA_CHANNEL_POSITION_AUX5: break;
-    case PA_CHANNEL_POSITION_AUX6: break;
-    case PA_CHANNEL_POSITION_AUX7: break;
-    case PA_CHANNEL_POSITION_AUX8: break;
-    case PA_CHANNEL_POSITION_AUX9: break;
-    case PA_CHANNEL_POSITION_AUX10: break;
-    case PA_CHANNEL_POSITION_AUX11: break;
-    case PA_CHANNEL_POSITION_AUX12: break;
-    case PA_CHANNEL_POSITION_AUX13: break;
-    case PA_CHANNEL_POSITION_AUX14: break;
-    case PA_CHANNEL_POSITION_AUX15: break;
-    case PA_CHANNEL_POSITION_AUX16: break;
-    case PA_CHANNEL_POSITION_AUX17: break;
-    case PA_CHANNEL_POSITION_AUX18: break;
-    case PA_CHANNEL_POSITION_AUX19: break;
-    case PA_CHANNEL_POSITION_AUX20: break;
-    case PA_CHANNEL_POSITION_AUX21: break;
-    case PA_CHANNEL_POSITION_AUX22: break;
-    case PA_CHANNEL_POSITION_AUX23: break;
-    case PA_CHANNEL_POSITION_AUX24: break;
-    case PA_CHANNEL_POSITION_AUX25: break;
-    case PA_CHANNEL_POSITION_AUX26: break;
-    case PA_CHANNEL_POSITION_AUX27: break;
-    case PA_CHANNEL_POSITION_AUX28: break;
-    case PA_CHANNEL_POSITION_AUX29: break;
-    case PA_CHANNEL_POSITION_AUX30: break;
-    case PA_CHANNEL_POSITION_AUX31: break;
-    case PA_CHANNEL_POSITION_TOP_CENTER: return al::make_optional(TopCenter);
-    case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
-    case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
-    case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
-    case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return al::make_optional(TopBackLeft);
-    case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return al::make_optional(TopBackRight);
-    case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return al::make_optional(TopBackCenter);
-    case PA_CHANNEL_POSITION_MAX: break;
-    }
-    WARN("Unexpected channel enum %d (%s)\n", chan, pa_channel_position_to_string(chan));
-    return al::nullopt;
-}
-
-void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap)
-{
-    device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
-    for(uint i{0};i < chanmap.channels;++i)
-    {
-        if(auto label = ChannelFromPulse(chanmap.map[i]))
-            device->RealOut.ChannelIndex[*label] = i;
-    }
-}
-
 
 /* *grumble* Don't use enums for bitflags. */
 constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs)
@@ -497,19 +441,13 @@ public:
 
 pa_context *PulseMainloop::connectContext(std::unique_lock<std::mutex> &plock)
 {
-    const char *name{"OpenAL Soft"};
-
-    const PathNamePair &binname = GetProcBinary();
-    if(!binname.fname.empty())
-        name = binname.fname.c_str();
-
     if(!mMainloop)
     {
         mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this};
         mCondVar.wait(plock, [this]() noexcept { return mMainloop; });
     }
 
-    pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), name)};
+    pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), nullptr)};
     if(!context) throw al::backend_exception{al::backend_error::OutOfMemory,
         "pa_context_new() failed"};
 
@@ -661,7 +599,7 @@ PulseMainloop gGlobalMainloop;
 
 
 struct PulsePlayback final : public BackendBase {
-    PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~PulsePlayback() override;
 
     void bufferAttrCallback(pa_stream *stream) noexcept;
@@ -698,6 +636,7 @@ struct PulsePlayback final : public BackendBase {
 
     al::optional<std::string> mDeviceName{al::nullopt};
 
+    bool mIs51Rear{false};
     pa_buffer_attr mAttr;
     pa_sample_spec mSpec;
 
@@ -770,15 +709,16 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
     struct ChannelMap {
         DevFmtChannels fmt;
         pa_channel_map map;
+        bool is_51rear;
     };
     static constexpr std::array<ChannelMap,7> chanmaps{{
-        { DevFmtX71, X71ChanMap },
-        { DevFmtX61, X61ChanMap },
-        { DevFmtX51, X51ChanMap },
-        { DevFmtX51Rear, X51RearChanMap },
-        { DevFmtQuad, QuadChanMap },
-        { DevFmtStereo, StereoChanMap },
-        { DevFmtMono, MonoChanMap }
+        { DevFmtX71, X71ChanMap, false },
+        { DevFmtX61, X61ChanMap, false },
+        { DevFmtX51, X51ChanMap, false },
+        { DevFmtX51, X51RearChanMap, true },
+        { DevFmtQuad, QuadChanMap, false },
+        { DevFmtStereo, StereoChanMap, false },
+        { DevFmtMono, MonoChanMap, false }
     }};
 
     if(eol)
@@ -795,9 +735,11 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
     {
         if(!mDevice->Flags.test(ChannelsRequest))
             mDevice->FmtChans = chaniter->fmt;
+        mIs51Rear = chaniter->is_51rear;
     }
     else
     {
+        mIs51Rear = false;
         char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{};
         pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map);
         WARN("Failed to find format for channel map:\n    %s\n", chanmap_str);
@@ -805,8 +747,8 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int
 
     if(info->active_port)
         TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
-    mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo
-        && info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0);
+    mDevice->Flags.set(DirectEar, (info->active_port
+        && strcmp(info->active_port->name, "analog-output-headphones") == 0));
 }
 
 void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
@@ -846,7 +788,8 @@ void PulsePlayback::open(const char *name)
     }
 
     auto plock = mMainloop.getUniqueLock();
-    mContext = mMainloop.connectContext(plock);
+    if(!mContext)
+        mContext = mMainloop.connectContext(plock);
 
     pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
         PA_STREAM_FIX_CHANNELS};
@@ -864,8 +807,18 @@ void PulsePlayback::open(const char *name)
         if(defname) pulse_name = defname->c_str();
     }
     TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
-    mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr,
-        BackendType::Playback);
+    pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec,
+        nullptr, BackendType::Playback)};
+    if(mStream)
+    {
+        pa_stream_set_state_callback(mStream, nullptr, nullptr);
+        pa_stream_set_moved_callback(mStream, nullptr, nullptr);
+        pa_stream_set_write_callback(mStream, nullptr, nullptr);
+        pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr);
+        pa_stream_disconnect(mStream);
+        pa_stream_unref(mStream);
+    }
+    mStream = stream;
 
     pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
     mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
@@ -934,10 +887,7 @@ bool PulsePlayback::reset()
         chanmap = QuadChanMap;
         break;
     case DevFmtX51:
-        chanmap = X51ChanMap;
-        break;
-    case DevFmtX51Rear:
-        chanmap = X51RearChanMap;
+        chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap);
         break;
     case DevFmtX61:
         chanmap = X61ChanMap;
@@ -946,7 +896,7 @@ bool PulsePlayback::reset()
         chanmap = X71ChanMap;
         break;
     }
-    SetChannelOrderFromMap(mDevice, chanmap);
+    setDefaultWFXChannelOrder();
 
     switch(mDevice->FmtType)
     {
@@ -1029,34 +979,34 @@ void PulsePlayback::start()
 {
     auto plock = mMainloop.getUniqueLock();
 
-    pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
-    pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
-        &mMainloop)};
-
-    /* Write some (silent) samples to fill the prebuf amount if needed. */
-    if(size_t prebuf{mAttr.prebuf})
+    /* Write some (silent) samples to fill the buffer before we start feeding
+     * it newly mixed samples.
+     */
+    if(size_t todo{pa_stream_writable_size(mStream)})
     {
-        prebuf = minz(prebuf, pa_stream_writable_size(mStream));
-
-        void *buf{pa_xmalloc(prebuf)};
+        void *buf{pa_xmalloc(todo)};
         switch(mSpec.format)
         {
         case PA_SAMPLE_U8:
-            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x80);
+            std::fill_n(static_cast<uint8_t*>(buf), todo, 0x80);
             break;
         case PA_SAMPLE_ALAW:
-            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0xD5);
+            std::fill_n(static_cast<uint8_t*>(buf), todo, 0xD5);
             break;
         case PA_SAMPLE_ULAW:
-            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x7f);
+            std::fill_n(static_cast<uint8_t*>(buf), todo, 0x7f);
             break;
         default:
-            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x00);
+            std::fill_n(static_cast<uint8_t*>(buf), todo, 0x00);
             break;
         }
-        pa_stream_write(mStream, buf, prebuf, pa_xfree, 0, PA_SEEK_RELATIVE);
+        pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE);
     }
 
+    pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
+    pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
+        &mMainloop)};
+
     mMainloop.waitForOperation(op, plock);
 }
 
@@ -1103,7 +1053,7 @@ ClockLatency PulsePlayback::getClockLatency()
 
 
 struct PulseCapture final : public BackendBase {
-    PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~PulseCapture() override;
 
     void streamStateCallback(pa_stream *stream) noexcept;
@@ -1217,9 +1167,6 @@ void PulseCapture::open(const char *name)
     case DevFmtX51:
         chanmap = X51ChanMap;
         break;
-    case DevFmtX51Rear:
-        chanmap = X51RearChanMap;
-        break;
     case DevFmtX61:
         chanmap = X61ChanMap;
         break;
@@ -1230,7 +1177,7 @@ void PulseCapture::open(const char *name)
         throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
             DevFmtChannelsString(mDevice->FmtChans)};
     }
-    SetChannelOrderFromMap(mDevice, chanmap);
+    setDefaultWFXChannelOrder();
 
     switch(mDevice->FmtType)
     {
@@ -1505,7 +1452,7 @@ std::string PulseBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new PulsePlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/pulseaudio.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_PULSEAUDIO_H
 #define BACKENDS_PULSEAUDIO_H
 
-#include "backends/base.h"
+#include "base.h"
 
 class PulseBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 37 - 32
libs/openal-soft/Alc/backends/sdl2.cpp

@@ -20,16 +20,16 @@
 
 #include "config.h"
 
-#include "backends/sdl2.h"
+#include "sdl2.h"
 
 #include <cassert>
 #include <cstdlib>
 #include <cstring>
 #include <string>
 
-#include "alcmain.h"
 #include "almalloc.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/device.h"
 #include "core/logging.h"
 
 #include <SDL2/SDL.h>
@@ -46,7 +46,7 @@ namespace {
 constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
 
 struct Sdl2Backend final : public BackendBase {
-    Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
+    Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { }
     ~Sdl2Backend() override;
 
     void audioCallback(Uint8 *stream, int len) noexcept;
@@ -99,59 +99,64 @@ 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>(mDevice->UpdateSize);
+    want.samples = static_cast<Uint16>(minu(mDevice->UpdateSize, 8192));
     want.callback = &Sdl2Backend::audioCallbackC;
     want.userdata = this;
 
     /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
      * necessarily the first in the list.
      */
+    SDL_AudioDeviceID devid;
     if(!name || strcmp(name, defaultDeviceName) == 0)
-        mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
-            SDL_AUDIO_ALLOW_ANY_CHANGE);
+        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)
-            mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
+            devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
                 SDL_AUDIO_ALLOW_ANY_CHANGE);
         else
-            mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
-                SDL_AUDIO_ALLOW_ANY_CHANGE);
+            devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
     }
-    if(mDeviceID == 0)
+    if(!devid)
         throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
 
-    mDevice->Frequency = static_cast<uint>(have.freq);
-
-    if(have.channels == 1)
-        mDevice->FmtChans = DevFmtMono;
-    else if(have.channels == 2)
-        mDevice->FmtChans = DevFmtStereo;
+    DevFmtChannels devchans{};
+    if(have.channels >= 2)
+        devchans = DevFmtStereo;
+    else if(have.channels == 1)
+        devchans = DevFmtMono;
     else
+    {
+        SDL_CloseAudioDevice(devid);
         throw al::backend_exception{al::backend_error::DeviceError,
             "Unhandled SDL channel count: %d", int{have.channels}};
+    }
 
+    DevFmtType devtype{};
     switch(have.format)
     {
-    case AUDIO_U8:     mDevice->FmtType = DevFmtUByte;  break;
-    case AUDIO_S8:     mDevice->FmtType = DevFmtByte;   break;
-    case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
-    case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort;  break;
-    case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt;    break;
-    case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat;  break;
+    case AUDIO_U8:     devtype = DevFmtUByte;  break;
+    case AUDIO_S8:     devtype = DevFmtByte;   break;
+    case AUDIO_U16SYS: devtype = DevFmtUShort; break;
+    case AUDIO_S16SYS: devtype = DevFmtShort;  break;
+    case AUDIO_S32SYS: devtype = DevFmtInt;    break;
+    case AUDIO_F32SYS: devtype = DevFmtFloat;  break;
     default:
+        SDL_CloseAudioDevice(devid);
         throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
             have.format};
     }
-    mDevice->UpdateSize = have.samples;
-    mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
 
-    mFrameSize = mDevice->frameSizeFromFmt();
-    mFrequency = mDevice->Frequency;
-    mFmtChans = mDevice->FmtChans;
-    mFmtType = mDevice->FmtType;
-    mUpdateSize = mDevice->UpdateSize;
+    if(mDeviceID)
+        SDL_CloseAudioDevice(mDeviceID);
+    mDeviceID = devid;
+
+    mFrameSize = BytesFromDevFmt(devtype) * have.channels;
+    mFrequency = static_cast<uint>(have.freq);
+    mFmtChans = devchans;
+    mFmtType = devtype;
+    mUpdateSize = have.samples;
 
     mDevice->DeviceName = name ? name : defaultDeviceName;
 }
@@ -162,7 +167,7 @@ bool Sdl2Backend::reset()
     mDevice->FmtChans = mFmtChans;
     mDevice->FmtType = mFmtType;
     mDevice->UpdateSize = mUpdateSize;
-    mDevice->BufferSize = mUpdateSize * 2;
+    mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
     setDefaultWFXChannelOrder();
     return true;
 }
@@ -208,7 +213,7 @@ std::string SDL2BackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new Sdl2Backend{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/sdl2.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_SDL2_H
 #define BACKENDS_SDL2_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct SDL2BackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 160 - 144
libs/openal-soft/Alc/backends/sndio.cpp

@@ -20,8 +20,9 @@
 
 #include "config.h"
 
-#include "backends/sndio.h"
+#include "sndio.h"
 
+#include <poll.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -29,8 +30,9 @@
 #include <thread>
 #include <functional>
 
-#include "alcmain.h"
-#include "alu.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
 #include "threads.h"
@@ -43,9 +45,14 @@ namespace {
 
 static const char sndio_device[] = "SndIO Default";
 
+struct SioPar : public sio_par {
+    SioPar() { sio_initpar(this); }
+
+    void clear() { sio_initpar(this); }
+};
 
 struct SndioPlayback final : public BackendBase {
-    SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~SndioPlayback() override;
 
     int mixerProc();
@@ -56,6 +63,7 @@ struct SndioPlayback final : public BackendBase {
     void stop() override;
 
     sio_hdl *mSndHandle{nullptr};
+    uint mFrameStep{};
 
     al::vector<al::byte> mBuffer;
 
@@ -74,16 +82,8 @@ SndioPlayback::~SndioPlayback()
 
 int SndioPlayback::mixerProc()
 {
-    sio_par par;
-    sio_initpar(&par);
-    if(!sio_getpar(mSndHandle, &par))
-    {
-        mDevice->handleDisconnect("Failed to get device parameters");
-        return 1;
-    }
-
-    const size_t frameStep{par.pchan};
-    const size_t frameSize{frameStep * par.bps};
+    const size_t frameStep{mFrameStep};
+    const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
 
     SetRTPriority();
     althrd_setname(MIXER_THREAD_NAME);
@@ -91,22 +91,20 @@ int SndioPlayback::mixerProc()
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
     {
-        al::byte *WritePtr{mBuffer.data()};
-        size_t len{mBuffer.size()};
+        al::span<al::byte> buffer{mBuffer};
 
-        mDevice->renderSamples(WritePtr, static_cast<uint>(len/frameSize), frameStep);
-        while(len > 0 && !mKillNow.load(std::memory_order_acquire))
+        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, WritePtr, len)};
+            size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
             if(wrote == 0)
             {
                 ERR("sio_write failed\n");
                 mDevice->handleDisconnect("Failed to write playback samples");
                 break;
             }
-
-            len -= wrote;
-            WritePtr += wrote;
+            buffer = buffer.subspan(wrote);
         }
     }
 
@@ -122,34 +120,24 @@ void SndioPlayback::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
-    mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
-    if(mSndHandle == nullptr)
+    sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
+    if(!sndHandle)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
 
+    if(mSndHandle)
+        sio_close(mSndHandle);
+    mSndHandle = sndHandle;
+
     mDevice->DeviceName = name;
 }
 
 bool SndioPlayback::reset()
 {
-    sio_par par;
-    sio_initpar(&par);
+    SioPar par;
 
-    par.rate = mDevice->Frequency;
-    switch(mDevice->FmtChans)
-    {
-    case DevFmtMono   : par.pchan = 1; break;
-    case DevFmtQuad   : par.pchan = 4; break;
-    case DevFmtX51Rear: // fall-through - "Similar to 5.1, except using rear channels instead of sides"
-    case DevFmtX51    : par.pchan = 6; break;
-    case DevFmtX61    : par.pchan = 7; break;
-    case DevFmtX71    : par.pchan = 8; break;
-
-    // fall back to stereo for Ambi3D
-    case DevFmtAmbi3D : // fall-through
-    case DevFmtStereo : par.pchan = 2; break;
-    }
-
-    switch(mDevice->FmtType)
+    auto tryfmt = mDevice->FmtType;
+retry_params:
+    switch(tryfmt)
     {
     case DevFmtByte:
         par.bits = 8;
@@ -159,7 +147,6 @@ bool SndioPlayback::reset()
         par.bits = 8;
         par.sig = 0;
         break;
-    case DevFmtFloat:
     case DevFmtShort:
         par.bits = 16;
         par.sig = 1;
@@ -168,6 +155,7 @@ bool SndioPlayback::reset()
         par.bits = 16;
         par.sig = 0;
         break;
+    case DevFmtFloat:
     case DevFmtInt:
         par.bits = 32;
         par.sig = 1;
@@ -177,70 +165,64 @@ bool SndioPlayback::reset()
         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;
 
-    if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
-    {
-        ERR("Failed to set device parameters\n");
-        return false;
-    }
-
-    if(par.bits != par.bps*8)
-    {
-        ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
-        return false;
-    }
-    if(par.le != SIO_LE_NATIVE)
-    {
-        ERR("Non-native-endian samples not supported (got %s-endian)\n",
-            par.le ? "little" : "big");
-        return false;
-    }
-
-    mDevice->Frequency = par.rate;
-
-    if(par.pchan < 2)
-    {
-        if(mDevice->FmtChans != DevFmtMono)
-        {
-            WARN("Got %u channel for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
-            mDevice->FmtChans = DevFmtMono;
-        }
+    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"};
     }
-    else if((par.pchan == 2 && mDevice->FmtChans != DevFmtStereo)
-        || par.pchan == 3
-        || (par.pchan == 4 && mDevice->FmtChans != DevFmtQuad)
-        || par.pchan == 5
-        || (par.pchan == 6 && mDevice->FmtChans != DevFmtX51 && mDevice->FmtChans != DevFmtX51Rear)
-        || (par.pchan == 7 && mDevice->FmtChans != DevFmtX61)
-        || (par.pchan == 8 && mDevice->FmtChans != DevFmtX71)
-        || par.pchan > 8)
-    {
-        WARN("Got %u channels for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
-        mDevice->FmtChans = DevFmtStereo;
+    catch(al::backend_exception &e) {
+        if(tryfmt == DevFmtShort)
+            throw;
+        par.clear();
+        tryfmt = DevFmtShort;
+        goto retry_params;
     }
 
-    if(par.bits == 8 && par.sig == 1)
-        mDevice->FmtType = DevFmtByte;
-    else if(par.bits == 8 && par.sig == 0)
-        mDevice->FmtType = DevFmtUByte;
-    else if(par.bits == 16 && par.sig == 1)
-        mDevice->FmtType = DevFmtShort;
-    else if(par.bits == 16 && par.sig == 0)
-        mDevice->FmtType = DevFmtUShort;
-    else if(par.bits == 32 && par.sig == 1)
-        mDevice->FmtType = DevFmtInt;
-    else if(par.bits == 32 && par.sig == 0)
-        mDevice->FmtType = DevFmtUInt;
+    if(par.bps == 1)
+        mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
+    else if(par.bps == 2)
+        mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
+    else if(par.bps == 4)
+        mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
     else
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
+
+    mFrameStep = par.pchan;
+    if(par.pchan != mDevice->channelsFromFmt())
     {
-        ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
-        return false;
+        WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
+            DevFmtChannelsString(mDevice->FmtChans));
+        if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
+        else mDevice->FmtChans = DevFmtStereo;
     }
+    mDevice->Frequency = par.rate;
 
     setDefaultChannelOrder();
 
@@ -287,8 +269,13 @@ void SndioPlayback::stop()
 }
 
 
+/* TODO: This could be improved by avoiding the ring buffer and record thread,
+ * counting the available samples with the sio_onmove callback and reading
+ * directly from the device. However, this depends on reasonable support for
+ * capture buffer sizes apps may request.
+ */
 struct SndioCapture final : public BackendBase {
-    SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~SndioCapture() override;
 
     int recordProc();
@@ -323,40 +310,65 @@ int SndioCapture::recordProc()
 
     const uint frameSize{mDevice->frameSizeFromFmt()};
 
+    int nfds_pre{sio_nfds(mSndHandle)};
+    if(nfds_pre <= 0)
+    {
+        mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
+        return 1;
+    }
+
+    auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
+
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
     {
-        auto data = mRing->getWriteVector();
-        size_t todo{data.first.len + data.second.len};
-        if(todo == 0)
+        /* Wait until there's some samples to read. */
+        const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
+        if(nfds <= 0)
         {
-            static char junk[4096];
-            sio_read(mSndHandle, junk,
-                minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
+            mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
+            break;
+        }
+        int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
+        if(pollres < 0)
+        {
+            if(errno == EINTR) continue;
+            mDevice->handleDisconnect("Poll error: %s", strerror(errno));
+            break;
+        }
+        if(pollres == 0)
             continue;
+
+        const int revents{sio_revents(mSndHandle, fds.get())};
+        if((revents&POLLHUP))
+        {
+            mDevice->handleDisconnect("Got POLLHUP from poll events");
+            break;
         }
+        if(!(revents&POLLIN))
+            continue;
 
-        size_t total{0u};
-        data.first.len  *= frameSize;
-        data.second.len *= frameSize;
-        todo = minz(todo, mDevice->UpdateSize) * frameSize;
-        while(total < todo)
+        auto data = mRing->getWriteVector();
+        al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
+        while(!buffer.empty())
         {
-            if(!data.first.len)
-                data.first = data.second;
+            size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
+            if(got == 0) break;
 
-            size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
-            if(!got)
+            mRing->writeAdvance(got / frameSize);
+            buffer = buffer.subspan(got);
+            if(buffer.empty())
             {
-                mDevice->handleDisconnect("Failed to read capture samples");
-                break;
+                data = mRing->getWriteVector();
+                buffer = {data.first.buf, data.first.len*frameSize};
             }
-
-            data.first.buf += got;
-            data.first.len -= got;
-            total += got;
         }
-        mRing->writeAdvance(total / frameSize);
+        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));
+        }
     }
 
     return 0;
@@ -371,76 +383,80 @@ void SndioCapture::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
-    mSndHandle = sio_open(nullptr, SIO_REC, 0);
+    mSndHandle = sio_open(nullptr, SIO_REC, true);
     if(mSndHandle == nullptr)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
 
-    sio_par par;
-    sio_initpar(&par);
-
+    SioPar par;
     switch(mDevice->FmtType)
     {
     case DevFmtByte:
-        par.bps = 1;
+        par.bits = 8;
         par.sig = 1;
         break;
     case DevFmtUByte:
-        par.bps = 1;
+        par.bits = 8;
         par.sig = 0;
         break;
     case DevFmtShort:
-        par.bps = 2;
+        par.bits = 16;
         par.sig = 1;
         break;
     case DevFmtUShort:
-        par.bps = 2;
+        par.bits = 16;
         par.sig = 0;
         break;
     case DevFmtInt:
-        par.bps = 4;
+        par.bits = 32;
         par.sig = 1;
         break;
     case DevFmtUInt:
-        par.bps = 4;
+        par.bits = 32;
         par.sig = 0;
         break;
     case DevFmtFloat:
         throw al::backend_exception{al::backend_error::DeviceError,
             "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
     }
-    par.bits = par.bps * 8;
+    par.bps = SIO_BPS(par.bits);
     par.le = SIO_LE_NATIVE;
-    par.msb = SIO_LE_NATIVE ? 0 : 1;
+    par.msb = 1;
     par.rchan = mDevice->channelsFromFmt();
     par.rate = mDevice->Frequency;
 
     par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
-    par.round = minu(par.appbufsz, mDevice->Frequency/40);
-
-    mDevice->UpdateSize = par.round;
-    mDevice->BufferSize = par.appbufsz;
+    par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
 
     if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
         throw al::backend_exception{al::backend_error::DeviceError,
             "Failed to set device praameters"};
 
-    if(par.bits != par.bps*8)
+    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,
             "Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
 
-    if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
-        || (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
-        || (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
-        || (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
-        || (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
-        || (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
-        || mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
+    auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
+    {
+        return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
+            || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
+            || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
+            || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
+            || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
+            || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
+    };
+    if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
+        || mDevice->Frequency != par.rate)
         throw al::backend_exception{al::backend_error::DeviceError,
             "Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
             DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
-            mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate};
+            mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate};
 
     mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
+    mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
+    mDevice->UpdateSize = par.round;
 
     setDefaultChannelOrder();
 
@@ -507,7 +523,7 @@ std::string SndIOBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new SndioPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/sndio.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_SNDIO_H
 #define BACKENDS_SNDIO_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct SndIOBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 27 - 23
libs/openal-soft/Alc/backends/solaris.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/solaris.h"
+#include "solaris.h"
 
 #include <sys/ioctl.h>
 #include <sys/types.h>
@@ -39,11 +39,10 @@
 #include <thread>
 #include <functional>
 
-#include "alcmain.h"
 #include "albyte.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "compat.h"
+#include "alc/alconfig.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "threads.h"
 #include "vector.h"
@@ -59,7 +58,7 @@ std::string solaris_driver{"/dev/audio"};
 
 
 struct SolarisBackend final : public BackendBase {
-    SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
     ~SolarisBackend() override;
 
     int mixerProc();
@@ -71,6 +70,7 @@ struct SolarisBackend final : public BackendBase {
 
     int mFd{-1};
 
+    uint mFrameStep{};
     al::vector<al::byte> mBuffer;
 
     std::atomic<bool> mKillNow{true};
@@ -148,11 +148,15 @@ void SolarisBackend::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
-    mFd = ::open(solaris_driver.c_str(), O_WRONLY);
-    if(mFd == -1)
+    int fd{::open(solaris_driver.c_str(), O_WRONLY)};
+    if(fd == -1)
         throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
             solaris_driver.c_str(), strerror(errno)};
 
+    if(mFd != -1)
+        ::close(mFd);
+    mFd = fd;
+
     mDevice->DeviceName = name;
 }
 
@@ -162,12 +166,7 @@ bool SolarisBackend::reset()
     AUDIO_INITINFO(&info);
 
     info.play.sample_rate = mDevice->Frequency;
-
-    if(mDevice->FmtChans != DevFmtMono)
-        mDevice->FmtChans = DevFmtStereo;
-    uint numChannels{mDevice->channelsFromFmt()};
-    info.play.channels = numChannels;
-
+    info.play.channels = mDevice->channelsFromFmt();
     switch(mDevice->FmtType)
     {
     case DevFmtByte:
@@ -189,9 +188,7 @@ bool SolarisBackend::reset()
         info.play.encoding = AUDIO_ENCODING_LINEAR;
         break;
     }
-
-    uint frameSize{numChannels * mDevice->bytesFromFmt()};
-    info.play.buffer_size = mDevice->BufferSize * frameSize;
+    info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
 
     if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
     {
@@ -201,9 +198,13 @@ bool SolarisBackend::reset()
 
     if(mDevice->channelsFromFmt() != info.play.channels)
     {
-        ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
-            info.play.channels);
-        return false;
+        if(info.play.channels >= 2)
+            mDevice->FmtChans = DevFmtStereo;
+        else if(info.play.channels == 1)
+            mDevice->FmtChans = DevFmtMono;
+        else
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "Got %u device channels", info.play.channels};
     }
 
     if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
@@ -220,13 +221,16 @@ bool SolarisBackend::reset()
         return false;
     }
 
+    uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
+    mFrameStep = info.play.channels;
     mDevice->Frequency = info.play.sample_rate;
-    mDevice->BufferSize = info.play.buffer_size / frameSize;
+    mDevice->BufferSize = info.play.buffer_size / frame_size;
+    /* How to get the actual period size/count? */
     mDevice->UpdateSize = mDevice->BufferSize / 2;
 
     setDefaultChannelOrder();
 
-    mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+    mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
     std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
 
     return true;
@@ -291,7 +295,7 @@ std::string SolarisBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new SolarisBackend{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/solaris.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_SOLARIS_H
 #define BACKENDS_SOLARIS_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct SolarisBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 214 - 248
libs/openal-soft/Alc/backends/wasapi.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/wasapi.h"
+#include "wasapi.h"
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
@@ -47,6 +47,7 @@
 #include <atomic>
 #include <chrono>
 #include <condition_variable>
+#include <cstring>
 #include <deque>
 #include <functional>
 #include <future>
@@ -56,10 +57,11 @@
 #include <vector>
 
 #include "albit.h"
-#include "alcmain.h"
-#include "alu.h"
-#include "compat.h"
-#include "converter.h"
+#include "alnumeric.h"
+#include "comptr.h"
+#include "core/converter.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
 #include "strutils.h"
@@ -118,7 +120,8 @@ constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
 
-#define DEVNAME_HEAD "OpenAL Soft on "
+constexpr char DevNameHead[] = "OpenAL Soft on ";
+constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1};
 
 
 /* Scales the given reftime value, rounding the result. */
@@ -129,69 +132,6 @@ inline uint RefTime2Samples(const ReferenceTime &val, uint srate)
 }
 
 
-template<typename T>
-class ComPtr {
-    T *mPtr{nullptr};
-
-public:
-    ComPtr() noexcept = default;
-    ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
-    ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
-    ComPtr(std::nullptr_t) noexcept { }
-    explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
-    ~ComPtr() { if(mPtr) mPtr->Release(); }
-
-    ComPtr& operator=(const ComPtr &rhs)
-    {
-        if(!rhs.mPtr)
-        {
-            if(mPtr)
-                mPtr->Release();
-            mPtr = nullptr;
-        }
-        else
-        {
-            rhs.mPtr->AddRef();
-            try {
-                if(mPtr)
-                    mPtr->Release();
-                mPtr = rhs.mPtr;
-            }
-            catch(...) {
-                rhs.mPtr->Release();
-                throw;
-            }
-        }
-        return *this;
-    }
-    ComPtr& operator=(ComPtr&& rhs)
-    {
-        if(mPtr)
-            mPtr->Release();
-        mPtr = rhs.mPtr;
-        rhs.mPtr = nullptr;
-        return *this;
-    }
-
-    operator bool() const noexcept { return mPtr != nullptr; }
-
-    T& operator*() const noexcept { return *mPtr; }
-    T* operator->() const noexcept { return mPtr; }
-    T* get() const noexcept { return mPtr; }
-    T** getPtr() noexcept { return &mPtr; }
-
-    T* release() noexcept
-    {
-        T *ret{mPtr};
-        mPtr = nullptr;
-        return ret;
-    }
-
-    void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
-    void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
-};
-
-
 class GuidPrinter {
     char mMsg[64];
 
@@ -238,10 +178,9 @@ struct DevMap {
 
 bool checkName(const al::vector<DevMap> &list, const std::string &name)
 {
-    return std::find_if(list.cbegin(), list.cend(),
-        [&name](const DevMap &entry) -> bool
-        { return entry.name == name; }
-    ) != list.cend();
+    auto match_name = [&name](const DevMap &entry) -> bool
+    { return entry.name == name; };
+    return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
 }
 
 al::vector<DevMap> PlaybackDevices;
@@ -253,8 +192,7 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device)
 {
     static constexpr char UnknownName[]{"Unknown Device Name"};
     static constexpr char UnknownGuid[]{"Unknown Device GUID"};
-    std::string name{DEVNAME_HEAD};
-    std::string guid;
+    std::string name, guid;
 
     ComPtr<IPropertyStore> ps;
     HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
@@ -297,26 +235,26 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device)
     return std::make_pair(std::move(name), std::move(guid));
 }
 
-void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
+EndpointFormFactor get_device_formfactor(IMMDevice *device)
 {
     ComPtr<IPropertyStore> ps;
-    HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
+    HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())};
     if(FAILED(hr))
     {
         WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
-        return;
+        return UnknownFormFactor;
     }
 
+    EndpointFormFactor formfactor{UnknownFormFactor};
     PropVariant pvform;
-    hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get());
+    hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
     if(FAILED(hr))
         WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
     else if(pvform->vt == VT_UI4)
-        *formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
-    else if(pvform->vt == VT_EMPTY)
-        *formfactor = UnknownFormFactor;
-    else
+        formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
+    else if(pvform->vt != VT_EMPTY)
         WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt);
+    return formfactor;
 }
 
 
@@ -484,26 +422,27 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format)
 
 enum class MsgType {
     OpenDevice,
+    ReopenDevice,
     ResetDevice,
     StartDevice,
     StopDevice,
     CloseDevice,
     EnumeratePlayback,
     EnumerateCapture,
-    QuitThread,
 
-    Count
+    Count,
+    QuitThread = Count
 };
 
 constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
     "Open Device",
+    "Reopen Device",
     "Reset Device",
     "Start Device",
     "Stop Device",
     "Close Device",
     "Enumerate Playback",
-    "Enumerate Capture",
-    "Quit"
+    "Enumerate Capture"
 };
 
 
@@ -511,7 +450,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
 struct WasapiProxy {
     virtual ~WasapiProxy() = default;
 
-    virtual HRESULT openProxy() = 0;
+    virtual HRESULT openProxy(const char *name) = 0;
     virtual void closeProxy() = 0;
 
     virtual HRESULT resetProxy() = 0;
@@ -521,19 +460,22 @@ struct WasapiProxy {
     struct Msg {
         MsgType mType;
         WasapiProxy *mProxy;
+        const char *mParam;
         std::promise<HRESULT> mPromise;
+
+        explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
     };
     static std::deque<Msg> mMsgQueue;
     static std::mutex mMsgQueueLock;
     static std::condition_variable mMsgQueueCond;
 
-    std::future<HRESULT> pushMessage(MsgType type)
+    std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr)
     {
         std::promise<HRESULT> promise;
         std::future<HRESULT> future{promise.get_future()};
         {
             std::lock_guard<std::mutex> _{mMsgQueueLock};
-            mMsgQueue.emplace_back(Msg{type, this, std::move(promise)});
+            mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
         }
         mMsgQueueCond.notify_one();
         return future;
@@ -545,19 +487,19 @@ struct WasapiProxy {
         std::future<HRESULT> future{promise.get_future()};
         {
             std::lock_guard<std::mutex> _{mMsgQueueLock};
-            mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)});
+            mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)});
         }
         mMsgQueueCond.notify_one();
         return future;
     }
 
-    static bool popMessage(Msg &msg)
+    static Msg popMessage()
     {
         std::unique_lock<std::mutex> lock{mMsgQueueLock};
         mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
-        msg = std::move(mMsgQueue.front());
+        Msg msg{std::move(mMsgQueue.front())};
         mMsgQueue.pop_front();
-        return msg.mType != MsgType::QuitThread;
+        return msg;
     }
 
     static int messageHandler(std::promise<HRESULT> *promise);
@@ -597,12 +539,11 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
 
     TRACE("Starting message loop\n");
     uint deviceCount{0};
-    Msg msg;
-    while(popMessage(msg))
+    while(Msg msg{popMessage()})
     {
-        TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
+        TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n",
             MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType),
-            decltype(std::declval<void*>()){msg.mProxy});
+            static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam));
 
         switch(msg.mType)
         {
@@ -611,7 +552,7 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
             if(++deviceCount == 1)
                 hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
             if(SUCCEEDED(hr))
-                hr = msg.mProxy->openProxy();
+                hr = msg.mProxy->openProxy(msg.mParam);
             msg.mPromise.set_value(hr);
 
             if(FAILED(hr))
@@ -621,6 +562,11 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
             }
             continue;
 
+        case MsgType::ReopenDevice:
+            hr = msg.mProxy->openProxy(msg.mParam);
+            msg.mPromise.set_value(hr);
+            continue;
+
         case MsgType::ResetDevice:
             hr = msg.mProxy->resetProxy();
             msg.mPromise.set_value(hr);
@@ -669,11 +615,11 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
                 CoUninitialize();
             continue;
 
-        default:
-            ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
-            msg.mPromise.set_value(E_FAIL);
-            continue;
+        case MsgType::QuitThread:
+            break;
         }
+        ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
+        msg.mPromise.set_value(E_FAIL);
     }
     TRACE("Message loop finished\n");
 
@@ -682,13 +628,13 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
 
 
 struct WasapiPlayback final : public BackendBase, WasapiProxy {
-    WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~WasapiPlayback() override;
 
     int mixerProc();
 
     void open(const char *name) override;
-    HRESULT openProxy() override;
+    HRESULT openProxy(const char *name) override;
     void closeProxy() override;
 
     bool reset() override;
@@ -700,8 +646,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy {
 
     ClockLatency getClockLatency() override;
 
-    std::wstring mDevId;
-
     HRESULT mOpenStatus{E_FAIL};
     ComPtr<IMMDevice> mMMDev{nullptr};
     ComPtr<IAudioClient> mClient{nullptr};
@@ -796,11 +740,14 @@ void WasapiPlayback::open(const char *name)
 {
     HRESULT hr{S_OK};
 
-    mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
-    if(mNotifyEvent == nullptr)
+    if(!mNotifyEvent)
     {
-        ERR("Failed to create notify events: %lu\n", GetLastError());
-        hr = E_FAIL;
+        mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+        if(mNotifyEvent == nullptr)
+        {
+            ERR("Failed to create notify events: %lu\n", GetLastError());
+            hr = E_FAIL;
+        }
     }
 
     if(SUCCEEDED(hr))
@@ -808,71 +755,75 @@ void WasapiPlayback::open(const char *name)
         if(name)
         {
             if(PlaybackDevices.empty())
-                pushMessage(MsgType::EnumeratePlayback).wait();
-
-            hr = E_FAIL;
-            auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
-                [name](const DevMap &entry) -> bool
-                { return entry.name == name || entry.endpoint_guid == name; });
-            if(iter == PlaybackDevices.cend())
+                pushMessage(MsgType::EnumeratePlayback);
+            if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
             {
-                const std::wstring wname{utf8_to_wstr(name)};
-                iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
-                    [&wname](const DevMap &entry) -> bool
-                    { return entry.devid == wname; });
-            }
-            if(iter == PlaybackDevices.cend())
-                WARN("Failed to find device name matching \"%s\"\n", name);
-            else
-            {
-                mDevId = iter->devid;
-                mDevice->DeviceName = iter->name;
-                hr = S_OK;
+                name += DevNameHeadLen;
+                if(*name == '\0')
+                    name = nullptr;
             }
         }
-    }
 
-    if(SUCCEEDED(hr))
-        hr = pushMessage(MsgType::OpenDevice).get();
-    mOpenStatus = hr;
+        if(SUCCEEDED(mOpenStatus))
+            hr = pushMessage(MsgType::ReopenDevice, name).get();
+        else
+        {
+            hr = pushMessage(MsgType::OpenDevice, name).get();
+            mOpenStatus = hr;
+        }
+    }
 
     if(FAILED(hr))
-    {
-        if(mNotifyEvent != nullptr)
-            CloseHandle(mNotifyEvent);
-        mNotifyEvent = nullptr;
-
-        mDevId.clear();
-
         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
             hr};
-    }
 }
 
-HRESULT WasapiPlayback::openProxy()
+HRESULT WasapiPlayback::openProxy(const char *name)
 {
+    const wchar_t *devid{nullptr};
+    if(name)
+    {
+        auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+            [name](const DevMap &entry) -> bool
+            { return entry.name == name || entry.endpoint_guid == name; });
+        if(iter == PlaybackDevices.cend())
+        {
+            const std::wstring wname{utf8_to_wstr(name)};
+            iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+                [&wname](const DevMap &entry) -> bool
+                { return entry.devid == wname; });
+        }
+        if(iter == PlaybackDevices.cend())
+        {
+            WARN("Failed to find device name matching \"%s\"\n", name);
+            return E_FAIL;
+        }
+        name = iter->name.c_str();
+        devid = iter->devid.c_str();
+    }
+
     void *ptr;
+    ComPtr<IMMDevice> mmdev;
     HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
         IID_IMMDeviceEnumerator, &ptr)};
     if(SUCCEEDED(hr))
     {
         ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
-        if(mDevId.empty())
-            hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr());
+        if(!devid)
+            hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr());
         else
-            hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
+            hr = enumerator->GetDevice(devid, mmdev.getPtr());
     }
-    if(SUCCEEDED(hr))
-        hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
-    if(SUCCEEDED(hr))
+    if(FAILED(hr))
     {
-        mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
-        if(mDevice->DeviceName.empty())
-            mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
+        WARN("Failed to open device \"%s\"\n", name?name:"(default)");
+        return hr;
     }
 
-    if(FAILED(hr))
-        mMMDev = nullptr;
+    mClient = nullptr;
+    mMMDev = std::move(mmdev);
+    if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
+    else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
 
     return hr;
 }
@@ -935,10 +886,9 @@ HRESULT WasapiPlayback::resetProxy()
             mDevice->FmtChans = DevFmtX71;
         else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
             mDevice->FmtChans = DevFmtX61;
-        else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
+        else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
+            || (chanmask&X51RearMask) == X5DOT1REAR))
             mDevice->FmtChans = DevFmtX51;
-        else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
-            mDevice->FmtChans = DevFmtX51Rear;
         else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
             mDevice->FmtChans = DevFmtQuad;
         else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
@@ -971,10 +921,6 @@ HRESULT WasapiPlayback::resetProxy()
         OutputType.Format.nChannels = 6;
         OutputType.dwChannelMask = X5DOT1;
         break;
-    case DevFmtX51Rear:
-        OutputType.Format.nChannels = 6;
-        OutputType.dwChannelMask = X5DOT1REAR;
-        break;
     case DevFmtX61:
         OutputType.Format.nChannels = 7;
         OutputType.dwChannelMask = X6DOT1;
@@ -1050,27 +996,60 @@ HRESULT WasapiPlayback::resetProxy()
         mDevice->Frequency = OutputType.Format.nSamplesPerSec;
         const uint32_t chancount{OutputType.Format.nChannels};
         const DWORD chanmask{OutputType.dwChannelMask};
-        if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
-            mDevice->FmtChans = DevFmtX71;
-        else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
-            mDevice->FmtChans = DevFmtX61;
-        else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
-            mDevice->FmtChans = DevFmtX51;
-        else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
-            mDevice->FmtChans = DevFmtX51Rear;
-        else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
-            mDevice->FmtChans = DevFmtQuad;
-        else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
-            mDevice->FmtChans = DevFmtStereo;
-        else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
-            mDevice->FmtChans = DevFmtMono;
-        else
+        /* Don't update the channel format if the requested format fits what's
+         * supported.
+         */
+        bool chansok{false};
+        if(mDevice->Flags.test(ChannelsRequest))
         {
-            ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
-                OutputType.dwChannelMask);
-            mDevice->FmtChans = DevFmtStereo;
-            OutputType.Format.nChannels = 2;
-            OutputType.dwChannelMask = STEREO;
+            switch(mDevice->FmtChans)
+            {
+            case DevFmtMono:
+                chansok = (chancount >= 1 && (chanmask&MonoMask) == MONO);
+                break;
+            case DevFmtStereo:
+                chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO);
+                break;
+            case DevFmtQuad:
+                chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD);
+                break;
+            case DevFmtX51:
+                chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
+                        || (chanmask&X51RearMask) == X5DOT1REAR));
+                break;
+            case DevFmtX61:
+                chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1);
+                break;
+            case DevFmtX71:
+                chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1);
+                break;
+            case DevFmtAmbi3D:
+                break;
+            }
+        }
+        if(!chansok)
+        {
+            if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
+                mDevice->FmtChans = DevFmtX71;
+            else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
+                mDevice->FmtChans = DevFmtX61;
+            else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
+                || (chanmask&X51RearMask) == X5DOT1REAR))
+                mDevice->FmtChans = DevFmtX51;
+            else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
+                mDevice->FmtChans = DevFmtQuad;
+            else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
+                mDevice->FmtChans = DevFmtStereo;
+            else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
+                mDevice->FmtChans = DevFmtMono;
+            else
+            {
+                ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
+                    OutputType.dwChannelMask);
+                mDevice->FmtChans = DevFmtStereo;
+                OutputType.Format.nChannels = 2;
+                OutputType.dwChannelMask = STEREO;
+            }
         }
 
         if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
@@ -1105,10 +1084,8 @@ HRESULT WasapiPlayback::resetProxy()
     }
     mFrameStep = OutputType.Format.nChannels;
 
-    EndpointFormFactor formfactor{UnknownFormFactor};
-    get_device_formfactor(mMMDev.get(), &formfactor);
-    mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo
-        && (formfactor == Headphones || formfactor == Headset));
+    const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())};
+    mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
 
     setChannelOrderFromWFXMask(OutputType.dwChannelMask);
 
@@ -1220,13 +1197,13 @@ ClockLatency WasapiPlayback::getClockLatency()
 
 
 struct WasapiCapture final : public BackendBase, WasapiProxy {
-    WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~WasapiCapture() override;
 
     int recordProc();
 
     void open(const char *name) override;
-    HRESULT openProxy() override;
+    HRESULT openProxy(const char *name) override;
     void closeProxy() override;
 
     HRESULT resetProxy() override;
@@ -1238,8 +1215,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy {
     void captureSamples(al::byte *buffer, uint samples) override;
     uint availableSamples() override;
 
-    std::wstring mDevId;
-
     HRESULT mOpenStatus{E_FAIL};
     ComPtr<IMMDevice> mMMDev{nullptr};
     ComPtr<IAudioClient> mClient{nullptr};
@@ -1376,45 +1351,21 @@ void WasapiCapture::open(const char *name)
         if(name)
         {
             if(CaptureDevices.empty())
-                pushMessage(MsgType::EnumerateCapture).wait();
-
-            hr = E_FAIL;
-            auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
-                [name](const DevMap &entry) -> bool
-                { return entry.name == name || entry.endpoint_guid == name; });
-            if(iter == CaptureDevices.cend())
-            {
-                const std::wstring wname{utf8_to_wstr(name)};
-                iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
-                    [&wname](const DevMap &entry) -> bool
-                    { return entry.devid == wname; });
-            }
-            if(iter == CaptureDevices.cend())
-                WARN("Failed to find device name matching \"%s\"\n", name);
-            else
+                pushMessage(MsgType::EnumerateCapture);
+            if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
             {
-                mDevId = iter->devid;
-                mDevice->DeviceName = iter->name;
-                hr = S_OK;
+                name += DevNameHeadLen;
+                if(*name == '\0')
+                    name = nullptr;
             }
         }
+        hr = pushMessage(MsgType::OpenDevice, name).get();
     }
-
-    if(SUCCEEDED(hr))
-        hr = pushMessage(MsgType::OpenDevice).get();
     mOpenStatus = hr;
 
     if(FAILED(hr))
-    {
-        if(mNotifyEvent != nullptr)
-            CloseHandle(mNotifyEvent);
-        mNotifyEvent = nullptr;
-
-        mDevId.clear();
-
         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
             hr};
-    }
 
     hr = pushMessage(MsgType::ResetDevice).get();
     if(FAILED(hr))
@@ -1425,30 +1376,50 @@ void WasapiCapture::open(const char *name)
     }
 }
 
-HRESULT WasapiCapture::openProxy()
+HRESULT WasapiCapture::openProxy(const char *name)
 {
+    const wchar_t *devid{nullptr};
+    if(name)
+    {
+        auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+            [name](const DevMap &entry) -> bool
+            { return entry.name == name || entry.endpoint_guid == name; });
+        if(iter == CaptureDevices.cend())
+        {
+            const std::wstring wname{utf8_to_wstr(name)};
+            iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+                [&wname](const DevMap &entry) -> bool
+                { return entry.devid == wname; });
+        }
+        if(iter == CaptureDevices.cend())
+        {
+            WARN("Failed to find device name matching \"%s\"\n", name);
+            return E_FAIL;
+        }
+        name = iter->name.c_str();
+        devid = iter->devid.c_str();
+    }
+
     void *ptr;
     HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
         IID_IMMDeviceEnumerator, &ptr)};
     if(SUCCEEDED(hr))
     {
         ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
-        if(mDevId.empty())
+        if(!devid)
             hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr());
         else
-            hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
+            hr = enumerator->GetDevice(devid, mMMDev.getPtr());
     }
-    if(SUCCEEDED(hr))
-        hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
-    if(SUCCEEDED(hr))
+    if(FAILED(hr))
     {
-        mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
-        if(mDevice->DeviceName.empty())
-            mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
+        WARN("Failed to open device \"%s\"\n", name?name:"(default)");
+        return hr;
     }
 
-    if(FAILED(hr))
-        mMMDev = nullptr;
+    mClient = nullptr;
+    if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
+    else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
 
     return hr;
 }
@@ -1496,10 +1467,6 @@ HRESULT WasapiCapture::resetProxy()
         InputType.Format.nChannels = 6;
         InputType.dwChannelMask = X5DOT1;
         break;
-    case DevFmtX51Rear:
-        InputType.Format.nChannels = 6;
-        InputType.dwChannelMask = X5DOT1REAR;
-        break;
     case DevFmtX61:
         InputType.Format.nChannels = 7;
         InputType.dwChannelMask = X6DOT1;
@@ -1567,7 +1534,7 @@ HRESULT WasapiCapture::resetProxy()
         CoTaskMemFree(wfx);
         wfx = nullptr;
 
-        auto validate_fmt = [](ALCdevice *device, uint32_t chancount, DWORD chanmask) noexcept
+        auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
             -> bool
         {
             switch(device->FmtChans)
@@ -1584,14 +1551,14 @@ HRESULT WasapiCapture::resetProxy()
                 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
             /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
             case DevFmtX51:
-            case DevFmtX51Rear:
                 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
                         || (chanmask&X51RearMask) == X5DOT1REAR));
             case DevFmtX61:
                 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
             case DevFmtX71:
                 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
-            case DevFmtAmbi3D: return (chanmask == 0 && device->channelsFromFmt());
+            case DevFmtAmbi3D:
+                return (chanmask == 0 && chancount == device->channelsFromFmt());
             }
             return false;
         };
@@ -1808,31 +1775,30 @@ bool WasapiBackendFactory::querySupport(BackendType type)
 std::string WasapiBackendFactory::probe(BackendType type)
 {
     std::string outnames;
-    auto add_device = [&outnames](const DevMap &entry) -> void
-    {
-        /* +1 to also append the null char (to ensure a null-separated list and
-         * double-null terminated list).
-         */
-        outnames.append(entry.name.c_str(), entry.name.length()+1);
-    };
-
     switch(type)
     {
     case BackendType::Playback:
         WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait();
-        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        for(const DevMap &entry : PlaybackDevices)
+        {
+            /* +1 to also append the null char (to ensure a null-separated list
+             * and double-null terminated list).
+             */
+            outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
+        }
         break;
 
     case BackendType::Capture:
         WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait();
-        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        for(const DevMap &entry : CaptureDevices)
+            outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
         break;
     }
 
     return outnames;
 }
 
-BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new WasapiPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/wasapi.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_WASAPI_H
 #define BACKENDS_WASAPI_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct WasapiBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 24 - 29
libs/openal-soft/Alc/backends/wave.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/wave.h"
+#include "wave.h"
 
 #include <algorithm>
 #include <atomic>
@@ -35,12 +35,11 @@
 
 #include "albit.h"
 #include "albyte.h"
-#include "alcmain.h"
-#include "alconfig.h"
+#include "alc/alconfig.h"
 #include "almalloc.h"
 #include "alnumeric.h"
-#include "alu.h"
-#include "compat.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "opthelpers.h"
 #include "strutils.h"
@@ -93,7 +92,7 @@ void fwrite32le(uint val, FILE *f)
 
 
 struct WaveBackend final : public BackendBase {
-    WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { }
     ~WaveBackend() override;
 
     int mixerProc();
@@ -132,8 +131,8 @@ int WaveBackend::mixerProc()
 
     int64_t done{0};
     auto start = std::chrono::steady_clock::now();
-    while(!mKillNow.load(std::memory_order_acquire) &&
-          mDevice->Connected.load(std::memory_order_acquire))
+    while(!mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
     {
         auto now = std::chrono::steady_clock::now();
 
@@ -150,29 +149,23 @@ int WaveBackend::mixerProc()
             mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
             done += mDevice->UpdateSize;
 
-            if_constexpr(al::endian::native != al::endian::little)
+            if(al::endian::native != al::endian::little)
             {
                 const uint bytesize{mDevice->bytesFromFmt()};
 
                 if(bytesize == 2)
                 {
-                    ushort *samples = reinterpret_cast<ushort*>(mBuffer.data());
-                    const size_t len{mBuffer.size() / 2};
-                    for(size_t i{0};i < len;i++)
-                    {
-                        const ushort samp{samples[i]};
-                        samples[i] = static_cast<ushort>((samp>>8) | (samp<<8));
-                    }
+                    const size_t len{mBuffer.size() & ~size_t{1}};
+                    for(size_t i{0};i < len;i+=2)
+                        std::swap(mBuffer[i], mBuffer[i+1]);
                 }
                 else if(bytesize == 4)
                 {
-                    uint *samples = reinterpret_cast<uint*>(mBuffer.data());
-                    const size_t len{mBuffer.size() / 4};
-                    for(size_t i{0};i < len;i++)
+                    const size_t len{mBuffer.size() & ~size_t{3}};
+                    for(size_t i{0};i < len;i+=4)
                     {
-                        const uint samp{samples[i]};
-                        samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
-                                     ((samp<<8)&0x00ff0000) | (samp<<24);
+                        std::swap(mBuffer[i  ], mBuffer[i+3]);
+                        std::swap(mBuffer[i+1], mBuffer[i+2]);
                     }
                 }
             }
@@ -204,8 +197,8 @@ int WaveBackend::mixerProc()
 
 void WaveBackend::open(const char *name)
 {
-    const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
-    if(!fname[0]) throw al::backend_exception{al::backend_error::NoDevice,
+    auto fname = ConfigValueStr(nullptr, "wave", "file");
+    if(!fname) throw al::backend_exception{al::backend_error::NoDevice,
         "No wave output filename"};
 
     if(!name)
@@ -214,17 +207,20 @@ void WaveBackend::open(const char *name)
         throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
             name};
 
+    /* There's only one "device", so if it's already open, we're done. */
+    if(mFile) return;
+
 #ifdef _WIN32
     {
-        std::wstring wname = utf8_to_wstr(fname);
+        std::wstring wname{utf8_to_wstr(fname->c_str())};
         mFile = _wfopen(wname.c_str(), L"wb");
     }
 #else
-    mFile = fopen(fname, "wb");
+    mFile = fopen(fname->c_str(), "wb");
 #endif
     if(!mFile)
         throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
-            fname, strerror(errno)};
+            fname->c_str(), strerror(errno)};
 
     mDevice->DeviceName = name;
 }
@@ -267,7 +263,6 @@ bool WaveBackend::reset()
     case DevFmtStereo: chanmask = 0x01 | 0x02; break;
     case DevFmtQuad:   chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
     case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
-    case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; 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 DevFmtAmbi3D:
@@ -392,7 +387,7 @@ std::string WaveBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new WaveBackend{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/wave.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_WAVE_H
 #define BACKENDS_WAVE_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct WaveBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 39 - 37
libs/openal-soft/Alc/backends/winmm.cpp

@@ -20,7 +20,7 @@
 
 #include "config.h"
 
-#include "backends/winmm.h"
+#include "winmm.h"
 
 #include <stdlib.h>
 #include <stdio.h>
@@ -38,9 +38,9 @@
 #include <algorithm>
 #include <functional>
 
-#include "alcmain.h"
-#include "alu.h"
-#include "compat.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "core/helpers.h"
 #include "core/logging.h"
 #include "ringbuffer.h"
 #include "strutils.h"
@@ -125,7 +125,7 @@ void ProbeCaptureDevices(void)
 
 
 struct WinMMPlayback final : public BackendBase {
-    WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
     ~WinMMPlayback() override;
 
     void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@@ -181,8 +181,6 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
     SetRTPriority();
     althrd_setname(MIXER_THREAD_NAME);
 
-    const size_t frame_step{mDevice->channelsFromFmt()};
-
     while(!mKillNow.load(std::memory_order_acquire)
         && mDevice->Connected.load(std::memory_order_acquire))
     {
@@ -196,9 +194,9 @@ FORCE_ALIGN int WinMMPlayback::mixerProc()
         size_t widx{mIdx};
         do {
             WAVEHDR &waveHdr = mWaveBuffer[widx];
-            widx = (widx+1) % mWaveBuffer.size();
+            if(++widx == mWaveBuffer.size()) widx = 0;
 
-            mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, frame_step);
+            mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, mFormat.nChannels);
             mWritable.fetch_sub(1, std::memory_order_acq_rel);
             waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
         } while(--todo);
@@ -223,40 +221,47 @@ void WinMMPlayback::open(const char *name)
             name};
     auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
 
+    DevFmtType fmttype{mDevice->FmtType};
 retry_open:
-    mFormat = WAVEFORMATEX{};
-    if(mDevice->FmtType == DevFmtFloat)
+    WAVEFORMATEX format{};
+    if(fmttype == DevFmtFloat)
     {
-        mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
-        mFormat.wBitsPerSample = 32;
+        format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+        format.wBitsPerSample = 32;
     }
     else
     {
-        mFormat.wFormatTag = WAVE_FORMAT_PCM;
-        if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
-            mFormat.wBitsPerSample = 8;
+        format.wFormatTag = WAVE_FORMAT_PCM;
+        if(fmttype == DevFmtUByte || fmttype == DevFmtByte)
+            format.wBitsPerSample = 8;
         else
-            mFormat.wBitsPerSample = 16;
+            format.wBitsPerSample = 16;
     }
-    mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
-    mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
-    mFormat.nSamplesPerSec = mDevice->Frequency;
-    mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
-    mFormat.cbSize = 0;
-
-    MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
+    format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+    format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8);
+    format.nSamplesPerSec = mDevice->Frequency;
+    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+    format.cbSize = 0;
+
+    HWAVEOUT outHandle{};
+    MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format,
         reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
         reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
     if(res != MMSYSERR_NOERROR)
     {
-        if(mDevice->FmtType == DevFmtFloat)
+        if(fmttype == DevFmtFloat)
         {
-            mDevice->FmtType = DevFmtShort;
+            fmttype = DevFmtShort;
             goto retry_open;
         }
         throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
     }
 
+    if(mOutHdl)
+        waveOutClose(mOutHdl);
+    mOutHdl = outHandle;
+    mFormat = format;
+
     mDevice->DeviceName = PlaybackDevices[DeviceID];
 }
 
@@ -297,7 +302,7 @@ bool WinMMPlayback::reset()
     }
 
     uint chanmask{};
-    if(mFormat.nChannels == 2)
+    if(mFormat.nChannels >= 2)
     {
         mDevice->FmtChans = DevFmtStereo;
         chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
@@ -314,7 +319,7 @@ bool WinMMPlayback::reset()
     }
     setChannelOrderFromWFXMask(chanmask);
 
-    uint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
+    uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()};
 
     al_free(mWaveBuffer[0].lpData);
     mWaveBuffer[0] = WAVEHDR{};
@@ -334,9 +339,8 @@ bool WinMMPlayback::reset()
 void WinMMPlayback::start()
 {
     try {
-        std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
-            [this](WAVEHDR &waveHdr) -> void
-            { waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); });
+        for(auto &waveHdr : mWaveBuffer)
+            waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
         mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
 
         mKillNow.store(false, std::memory_order_release);
@@ -356,15 +360,14 @@ void WinMMPlayback::stop()
 
     while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
         mSem.wait();
-    std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
-        [this](WAVEHDR &waveHdr) -> void
-        { waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); });
+    for(auto &waveHdr : mWaveBuffer)
+        waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR));
     mWritable.store(0, std::memory_order_release);
 }
 
 
 struct WinMMCapture final : public BackendBase {
-    WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { }
     ~WinMMCapture() override;
 
     void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
@@ -471,7 +474,6 @@ void WinMMCapture::open(const char *name)
 
     case DevFmtQuad:
     case DevFmtX51:
-    case DevFmtX51Rear:
     case DevFmtX61:
     case DevFmtX71:
     case DevFmtAmbi3D:
@@ -615,7 +617,7 @@ std::string WinMMBackendFactory::probe(BackendType type)
     return outnames;
 }
 
-BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
+BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type)
 {
     if(type == BackendType::Playback)
         return BackendPtr{new WinMMPlayback{device}};

+ 2 - 2
libs/openal-soft/Alc/backends/winmm.h

@@ -1,7 +1,7 @@
 #ifndef BACKENDS_WINMM_H
 #define BACKENDS_WINMM_H
 
-#include "backends/base.h"
+#include "base.h"
 
 struct WinMMBackendFactory final : public BackendFactory {
 public:
@@ -11,7 +11,7 @@ public:
 
     std::string probe(BackendType type) override;
 
-    BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+    BackendPtr createBackend(DeviceBase *device, BackendType type) override;
 
     static BackendFactory &getFactory();
 };

+ 0 - 9
libs/openal-soft/Alc/compat.h

@@ -1,9 +0,0 @@
-#ifndef AL_COMPAT_H
-#define AL_COMPAT_H
-
-#include <string>
-
-struct PathNamePair { std::string path, fname; };
-const PathNamePair &GetProcBinary(void);
-
-#endif /* AL_COMPAT_H */

+ 1299 - 0
libs/openal-soft/Alc/context.cpp

@@ -0,0 +1,1299 @@
+
+#include "config.h"
+
+#include "context.h"
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <numeric>
+#include <stddef.h>
+#include <stdexcept>
+
+#include "AL/efx.h"
+
+#include "al/auxeffectslot.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 "core/async_event.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/logging.h"
+#include "core/voice.h"
+#include "core/voice_change.h"
+#include "device.h"
+#include "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"
+#endif // ALSOFT_EAX
+
+namespace {
+
+using namespace std::placeholders;
+
+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";
+
+} // namespace
+
+
+std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
+
+thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
+ALCcontext::ThreadCtx::~ThreadCtx()
+{
+    if(ALCcontext *ctx{ALCcontext::sLocalContext})
+    {
+        const bool result{ctx->releaseIfNoDelete()};
+        ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
+            result ? "" : ", leak detected");
+    }
+}
+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()
+{
+    TRACE("Freeing context %p\n", voidp{this});
+
+    size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
+        [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
+    if(count > 0)
+        WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
+    mSourceList.clear();
+    mNumSources = 0;
+
+#ifdef ALSOFT_EAX
+    eax_uninitialize();
+#endif // ALSOFT_EAX
+
+    mDefaultSlot = nullptr;
+    count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
+        [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
+    if(count > 0)
+        WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
+    mEffectSlotList.clear();
+    mNumEffectSlots = 0;
+}
+
+void ALCcontext::init()
+{
+    if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback)
+    {
+        mDefaultSlot = std::make_unique<ALeffectslot>();
+        aluInitEffectPanning(&mDefaultSlot->mSlot, this);
+    }
+
+    EffectSlotArray *auxslots;
+    if(!mDefaultSlot)
+        auxslots = EffectSlot::CreatePtrArray(0);
+    else
+    {
+        auxslots = EffectSlot::CreatePtrArray(1);
+        (*auxslots)[0] = &mDefaultSlot->mSlot;
+        mDefaultSlot->mState = SlotState::Playing;
+    }
+    mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
+
+    allocVoiceChanges();
+    {
+        VoiceChange *cur{mVoiceChangeTail};
+        while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
+            cur = next;
+        mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
+    }
+
+    mExtensionList = alExtList;
+
+#ifdef ALSOFT_EAX
+    eax_initialize_extensions();
+#endif // ALSOFT_EAX
+
+    mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f};
+    mParams.Matrix = alu::Matrix::Identity();
+    mParams.Velocity = alu::Vector{};
+    mParams.Gain = mListener.Gain;
+    mParams.MetersPerUnit = mListener.mMetersPerUnit;
+    mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF;
+    mParams.DopplerFactor = mDopplerFactor;
+    mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
+    mParams.SourceDistanceModel = mSourceDistanceModel;
+    mParams.mDistanceModel = mDistanceModel;
+
+
+    mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
+    StartEventThrd(this);
+
+
+    allocVoices(256);
+    mActiveVoiceCount.store(64, std::memory_order_relaxed);
+}
+
+bool ALCcontext::deinit()
+{
+    if(sLocalContext == this)
+    {
+        WARN("%p released while current on thread\n", voidp{this});
+        sThreadContext.set(nullptr);
+        release();
+    }
+
+    ALCcontext *origctx{this};
+    if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
+        release();
+
+    bool ret{};
+    /* 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);
+
+        /* 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));
+
+        /* 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;
+        }
+
+        ret = !newarray->empty();
+    }
+    else
+        ret = !oldarray->empty();
+
+    StopEventThrd(this);
+
+    return ret;
+}
+
+void ALCcontext::applyAllUpdates()
+{
+    /* Tell the mixer to stop applying updates, then wait for any active
+     * updating to finish, before providing updates.
+     */
+    mHoldUpdates.store(true, std::memory_order_release);
+    while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
+        /* busy-wait */
+    }
+
+#ifdef ALSOFT_EAX
+    eax_apply_deferred();
+#endif
+    if(std::exchange(mPropsDirty, false))
+        UpdateContextProps(this);
+    UpdateAllEffectSlotProps(this);
+    UpdateAllSourceProps(this);
+
+    /* Now with all updates declared, let the mixer continue applying them so
+     * they all happen at once.
+     */
+    mHoldUpdates.store(false, std::memory_order_release);
+}
+
+#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)
+{
+    for(auto &sublist : context->mSourceList)
+    {
+        uint64_t usemask{~sublist.FreeMask};
+        while(usemask)
+        {
+            const int idx{al::countr_zero(usemask)};
+            usemask &= ~(1_u64 << idx);
+
+            func(sublist.Sources[idx]);
+        }
+    }
+}
+
+} // namespace
+
+
+bool ALCcontext::eax_is_capable() const noexcept
+{
+    return eax_has_enough_aux_sends();
+}
+
+void ALCcontext::eax_uninitialize() noexcept
+{
+    if (!eax_is_initialized_)
+    {
+        return;
+    }
+
+    eax_is_initialized_ = true;
+    eax_is_tried_ = false;
+
+    eax_fx_slots_.uninitialize();
+}
+
+ALenum ALCcontext::eax_eax_set(
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_value,
+    ALuint property_value_size)
+{
+    eax_initialize();
+
+    const auto eax_call = create_eax_call(
+        false,
+        property_set_id,
+        property_id,
+        property_source_id,
+        property_value,
+        property_value_size
+    );
+
+    eax_unlock_legacy_fx_slots(eax_call);
+
+    switch (eax_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.");
+    }
+
+    static constexpr auto deferred_flag = 0x80000000u;
+    if(!(property_id&deferred_flag) && !mDeferUpdates)
+        applyAllUpdates();
+
+    return AL_NO_ERROR;
+}
+
+ALenum ALCcontext::eax_eax_get(
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_value,
+    ALuint property_value_size)
+{
+    eax_initialize();
+
+    const auto eax_call = create_eax_call(
+        true,
+        property_set_id,
+        property_id,
+        property_source_id,
+        property_value,
+        property_value_size
+    );
+
+    eax_unlock_legacy_fx_slots(eax_call);
+
+    switch (eax_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.");
+    }
+
+    return AL_NO_ERROR;
+}
+
+void ALCcontext::eax_update_filters()
+{
+    ForEachSource(this, std::mem_fn(&ALsource::eax_update_filters));
+}
+
+void ALCcontext::eax_commit_and_update_sources()
+{
+    std::unique_lock<std::mutex> source_lock{mSourceLock};
+    ForEachSource(this, std::mem_fn(&ALsource::eax_commit_and_update));
+}
+
+void ALCcontext::eax_set_last_error() noexcept
+{
+    eax_last_error_ = EAXERR_INVALID_OPERATION;
+}
+
+[[noreturn]]
+void ALCcontext::eax_fail(
+    const char* message)
+{
+    throw ContextException{message};
+}
+
+void ALCcontext::eax_initialize_extensions()
+{
+    if (!eax_g_is_enabled)
+    {
+        return;
+    }
+
+    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;
+
+    eax_extension_list_.reserve(string_max_capacity);
+
+    if (eax_is_capable())
+    {
+        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_ += ' ';
+    }
+
+    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_)
+    {
+        return;
+    }
+
+    if (eax_is_tried_)
+    {
+        eax_fail("No EAX.");
+    }
+
+    eax_is_tried_ = true;
+
+    if (!eax_g_is_enabled)
+    {
+        eax_fail("EAX disabled by a configuration.");
+    }
+
+    eax_ensure_compatibility();
+    eax_set_defaults();
+    eax_set_air_absorbtion_hf();
+    eax_update_speaker_configuration();
+    eax_initialize_fx_slots();
+    eax_initialize_sources();
+
+    eax_is_initialized_ = true;
+}
+
+bool ALCcontext::eax_has_no_default_effect_slot() const noexcept
+{
+    return mDefaultSlot == nullptr;
+}
+
+void ALCcontext::eax_ensure_no_default_effect_slot() const
+{
+    if (!eax_has_no_default_effect_slot())
+    {
+        eax_fail("There is a default effect slot in the context.");
+    }
+}
+
+bool ALCcontext::eax_has_enough_aux_sends() const noexcept
+{
+    return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS;
+}
+
+void ALCcontext::eax_ensure_enough_aux_sends() const
+{
+    if (!eax_has_enough_aux_sends())
+    {
+        eax_fail("Not enough aux sends.");
+    }
+}
+
+void ALCcontext::eax_ensure_compatibility()
+{
+    eax_ensure_enough_aux_sends();
+}
+
+unsigned long ALCcontext::eax_detect_speaker_configuration() const
+{
+#define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]"
+
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono: return SPEAKERS_2;
+    case DevFmtStereo:
+        /* Pretend 7.1 if using UHJ output, since they both provide full
+         * horizontal surround.
+         */
+        if(mDevice->mUhjEncoder)
+            return SPEAKERS_7;
+        if(mDevice->Flags.test(DirectEar))
+            return HEADPHONES;
+        return SPEAKERS_2;
+    case DevFmtQuad: return SPEAKERS_4;
+    case DevFmtX51: return SPEAKERS_5;
+    case DevFmtX61: return SPEAKERS_6;
+    case DevFmtX71: return SPEAKERS_7;
+    /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D
+     * provide full-sphere surround sound. Depends if apps are more likely to
+     * consider headphones or 7.1 for surround sound support.
+     */
+    case DevFmtAmbi3D: return SPEAKERS_7;
+    }
+    ERR(EAX_PREFIX "Unexpected device channel format 0x%x.\n", mDevice->FmtChans);
+    return HEADPHONES;
+
+#undef EAX_PREFIX
+}
+
+void ALCcontext::eax_update_speaker_configuration()
+{
+    eax_speaker_config_ = eax_detect_speaker_configuration();
+}
+
+void ALCcontext::eax_set_last_error_defaults() noexcept
+{
+    eax_last_error_ = EAX_OK;
+}
+
+void ALCcontext::eax_set_session_defaults() noexcept
+{
+    eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION;
+    eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS;
+}
+
+void ALCcontext::eax_set_context_defaults() noexcept
+{
+    eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID;
+    eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR;
+    eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF;
+    eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE;
+}
+
+void ALCcontext::eax_set_defaults() noexcept
+{
+    eax_set_last_error_defaults();
+    eax_set_session_defaults();
+    eax_set_context_defaults();
+
+    eax_d_ = eax_;
+}
+
+void ALCcontext::eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) 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();
+}
+
+void ALCcontext::eax_dispatch_fx_slot(
+    const EaxEaxCall& eax_call)
+{
+    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();
+    }
+}
+
+void ALCcontext::eax_dispatch_source(
+    const EaxEaxCall& eax_call)
+{
+    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);
+}
+
+void ALCcontext::eax_get_primary_fx_slot_id(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_.context.guidPrimaryFXSlotID);
+}
+
+void ALCcontext::eax_get_distance_factor(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_.context.flDistanceFactor);
+}
+
+void ALCcontext::eax_get_air_absorption_hf(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_.context.flAirAbsorptionHF);
+}
+
+void ALCcontext::eax_get_hf_reference(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_.context.flHFReference);
+}
+
+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);
+}
+
+void ALCcontext::eax_get_speaker_config(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_speaker_config_);
+}
+
+void ALCcontext::eax_get_session(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_session_);
+}
+
+void ALCcontext::eax_get_macro_fx_factor(
+    const EaxEaxCall& eax_call)
+{
+    eax_call.set_value<ContextException>(eax_.context.flMacroFXFactor);
+}
+
+void ALCcontext::eax_get_context_all(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_version())
+    {
+        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.");
+    }
+}
+
+void ALCcontext::eax_get(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_id())
+    {
+        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.");
+    }
+}
+
+void ALCcontext::eax_set_primary_fx_slot_id()
+{
+    eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_;
+    eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID;
+}
+
+void ALCcontext::eax_set_distance_factor()
+{
+    mListener.mMetersPerUnit = eax_.context.flDistanceFactor;
+    mPropsDirty = true;
+}
+
+void ALCcontext::eax_set_air_absorbtion_hf()
+{
+    mAirAbsorptionGainHF = level_mb_to_gain(eax_.context.flAirAbsorptionHF);
+    mPropsDirty = true;
+}
+
+void ALCcontext::eax_set_hf_reference()
+{
+    // TODO
+}
+
+void ALCcontext::eax_set_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);
+}
+
+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_); };
+    ForEachSource(this, update_source);
+}
+
+void ALCcontext::eax_validate_primary_fx_slot_id(
+    const GUID& primary_fx_slot_id)
+{
+    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)
+    {
+        eax_fail("Unsupported primary FX slot id.");
+    }
+}
+
+void ALCcontext::eax_validate_distance_factor(
+    float distance_factor)
+{
+    eax_validate_range<ContextException>(
+        "Distance Factor",
+        distance_factor,
+        EAXCONTEXT_MINDISTANCEFACTOR,
+        EAXCONTEXT_MAXDISTANCEFACTOR);
+}
+
+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);
+}
+
+void ALCcontext::eax_validate_hf_reference(
+    float hf_reference)
+{
+    eax_validate_range<ContextException>(
+        "HF Reference",
+        hf_reference,
+        EAXCONTEXT_MINHFREFERENCE,
+        EAXCONTEXT_MAXHFREFERENCE);
+}
+
+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.");
+    }
+}
+
+void ALCcontext::eax_validate_session_eax_version(
+    unsigned long eax_version)
+{
+    switch (eax_version)
+    {
+        case EAX_40:
+        case EAX_50:
+            break;
+
+        default:
+            eax_fail("Unsupported session EAX version.");
+    }
+}
+
+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)
+{
+    eax_d_.context.flAirAbsorptionHF = air_absorption_hf;
+
+    eax_context_dirty_flags_.flAirAbsorptionHF =
+        (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF);
+}
+
+void ALCcontext::eax_defer_hf_reference(
+    float hf_reference)
+{
+    eax_d_.context.flHFReference = hf_reference;
+
+    eax_context_dirty_flags_.flHFReference =
+        (eax_.context.flHFReference != eax_d_.context.flHFReference);
+}
+
+void ALCcontext::eax_defer_macro_fx_factor(
+    float macro_fx_factor)
+{
+    eax_d_.context.flMacroFXFactor = macro_fx_factor;
+
+    eax_context_dirty_flags_.flMacroFXFactor =
+        (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor);
+}
+
+void ALCcontext::eax_defer_context_all(
+    const EAX40CONTEXTPROPERTIES& context_all)
+{
+    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())
+    {
+    case 4:
+        {
+            const auto& context_all =
+                eax_call.get_value<ContextException, EAX40CONTEXTPROPERTIES>();
+
+            eax_validate_context_all(context_all);
+            eax_defer_context_all(context_all);
+        }
+        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);
+        }
+        break;
+
+    default:
+        eax_fail("Unsupported EAX version.");
+    }
+}
+
+void ALCcontext::eax_defer_primary_fx_slot_id(
+    const EaxEaxCall& eax_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);
+}
+
+void ALCcontext::eax_defer_air_absorption_hf(
+    const EaxEaxCall& eax_call)
+{
+    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);
+}
+
+void ALCcontext::eax_defer_hf_reference(
+    const EaxEaxCall& eax_call)
+{
+    const auto& hf_reference =
+        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flHFReference)>();
+
+    eax_validate_hf_reference(hf_reference);
+    eax_defer_hf_reference(hf_reference);
+}
+
+void ALCcontext::eax_set_session(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_session =
+        eax_call.get_value<ContextException, const EAXSESSIONPROPERTIES>();
+
+    eax_validate_session(eax_session);
+
+    eax_session_ = eax_session;
+}
+
+void ALCcontext::eax_defer_macro_fx_factor(
+    const EaxEaxCall& eax_call)
+{
+    const auto& macro_fx_factor =
+        eax_call.get_value<ContextException, const decltype(EAX50CONTEXTPROPERTIES::flMacroFXFactor)>();
+
+    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())
+    {
+        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.");
+    }
+}
+
+void ALCcontext::eax_apply_deferred()
+{
+    if (eax_context_dirty_flags_ == ContextDirtyFlags{})
+    {
+        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 (eax_context_dirty_flags_.flDistanceFactor)
+    {
+        eax_set_distance_factor();
+    }
+
+    if (eax_context_dirty_flags_.flAirAbsorptionHF)
+    {
+        eax_set_air_absorbtion_hf();
+    }
+
+    if (eax_context_dirty_flags_.flHFReference)
+    {
+        eax_set_hf_reference();
+    }
+
+    if (eax_context_dirty_flags_.flMacroFXFactor)
+    {
+        eax_set_macro_fx_factor();
+    }
+
+    if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{})
+    {
+        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)
+{
+    throw EaxSetException{message};
+}
+
+
+class EaxGetException :
+    public EaxException
+{
+public:
+    explicit EaxGetException(
+        const char* message)
+        :
+        EaxException{"EAX_GET", message}
+    {
+    }
+}; // EaxGetException
+
+
+[[noreturn]]
+void eax_fail_get(
+    const char* message)
+{
+    throw EaxGetException{message};
+}
+
+
+} // 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
+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
+    );
+}
+catch (...)
+{
+    eax_log_exception(__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
+{
+    auto context = GetContextRef();
+
+    if (!context)
+    {
+        eax_fail_get("No current context.");
+    }
+
+    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
+    );
+}
+catch (...)
+{
+    eax_log_exception(__func__);
+    return AL_INVALID_OPERATION;
+}
+#endif // ALSOFT_EAX

+ 504 - 0
libs/openal-soft/Alc/context.h

@@ -0,0 +1,504 @@
+#ifndef ALC_CONTEXT_H
+#define ALC_CONTEXT_H
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <stdint.h>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "al/listener.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "atomic.h"
+#include "core/context.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
+#endif // ALSOFT_EAX
+
+struct ALeffect;
+struct ALeffectslot;
+struct ALsource;
+
+using uint = unsigned int;
+
+
+struct SourceSubList {
+    uint64_t FreeMask{~0_u64};
+    ALsource *Sources{nullptr}; /* 64 */
+
+    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; }
+};
+
+struct EffectSlotSubList {
+    uint64_t FreeMask{~0_u64};
+    ALeffectslot *EffectSlots{nullptr}; /* 64 */
+
+    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; }
+};
+
+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};
+
+    DistanceModel mDistanceModel{DistanceModel::Default};
+    bool mSourceDistanceModel{false};
+
+    float mDopplerFactor{1.0f};
+    float mDopplerVelocity{1.0f};
+    float mSpeedOfSound{SpeedOfSoundMetersPerSec};
+    float mAirAbsorptionGainHF{AirAbsorbGainHF};
+
+    std::mutex mEventCbLock;
+    ALEVENTPROCSOFT mEventCb{};
+    void *mEventParam{nullptr};
+
+    ALlistener mListener{};
+
+    al::vector<SourceSubList> mSourceList;
+    ALuint mNumSources{0};
+    std::mutex mSourceLock;
+
+    al::vector<EffectSlotSubList> mEffectSlotList;
+    ALuint mNumEffectSlots{0u};
+    std::mutex mEffectSlotLock;
+
+    /* Default effect slot */
+    std::unique_ptr<ALeffectslot> mDefaultSlot;
+
+    const char *mExtensionList{nullptr};
+
+
+    ALCcontext(al::intrusive_ptr<ALCdevice> device);
+    ALCcontext(const ALCcontext&) = delete;
+    ALCcontext& operator=(const ALCcontext&) = delete;
+    ~ALCcontext();
+
+    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.
+     */
+    bool deinit();
+
+    /**
+     * Defers/suspends updates for the given context's listener and sources.
+     * This does *NOT* stop mixing, but rather prevents certain property
+     * changes from taking effect. mPropLock must be held when called.
+     */
+    void deferUpdates() noexcept { mDeferUpdates = true; }
+
+    /**
+     * Resumes update processing after being deferred. mPropLock must be held
+     * when called.
+     */
+    void processUpdates()
+    {
+        if(std::exchange(mDeferUpdates, false))
+            applyAllUpdates();
+    }
+
+    /**
+     * Applies all pending updates for the context, listener, effect slots, and
+     * sources.
+     */
+    void applyAllUpdates();
+
+#ifdef __USE_MINGW_ANSI_STDIO
+    [[gnu::format(gnu_printf, 3, 4)]]
+#else
+    [[gnu::format(printf, 3, 4)]]
+#endif
+    void setError(ALenum errorCode, const char *msg, ...);
+
+    /* Process-wide current context */
+    static std::atomic<ALCcontext*> sGlobalContext;
+
+private:
+    /* Thread-local current context. */
+    static thread_local ALCcontext *sLocalContext;
+
+    /* Thread-local context handling. This handles attempting to release the
+     * context which may have been left current when the thread is destroyed.
+     */
+    class ThreadCtx {
+    public:
+        ~ThreadCtx();
+        void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; }
+    };
+    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;
+
+
+    ALenum eax_eax_set(
+        const GUID* property_set_id,
+        ALuint property_id,
+        ALuint property_source_id,
+        ALvoid* property_value,
+        ALuint property_value_size);
+
+    ALenum eax_eax_get(
+        const GUID* property_set_id,
+        ALuint property_id,
+        ALuint property_source_id,
+        ALvoid* property_value,
+        ALuint property_value_size);
+
+
+    void eax_update_filters();
+
+    void eax_commit_and_update_sources();
+
+
+    void eax_set_last_error() noexcept;
+
+
+    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_; }
+
+    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); }
+
+    void eax_commit_fx_slots()
+    { eax_fx_slots_.commit(); }
+
+private:
+    struct Eax
+    {
+        EAX50CONTEXTPROPERTIES context{};
+    }; // Eax
+
+
+    bool eax_is_initialized_{};
+    bool eax_is_tried_{};
+    bool eax_are_legacy_fx_slots_unlocked_{};
+
+    long eax_last_error_{};
+    unsigned long eax_speaker_config_{};
+
+    EaxFxSlotIndex eax_previous_primary_fx_slot_index_{};
+    EaxFxSlotIndex eax_primary_fx_slot_index_{};
+    EaxFxSlots eax_fx_slots_{};
+
+    EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{};
+
+    Eax eax_{};
+    Eax eax_d_{};
+    EAXSESSIONPROPERTIES eax_session_{};
+
+    ContextDirtyFlags eax_context_dirty_flags_{};
+
+    std::string eax_extension_list_{};
+
+
+    [[noreturn]]
+    static void eax_fail(
+        const char* message);
+
+
+    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_initialize_fx_slots();
+
+
+    void eax_update_sources();
+
+
+    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();
+#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);
+
+void UpdateContextProps(ALCcontext *context);
+
+
+extern bool TrapALError;
+
+
+#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;
+#endif // ALSOFT_EAX
+
+#endif /* ALC_CONTEXT_H */

+ 90 - 0
libs/openal-soft/Alc/device.cpp

@@ -0,0 +1,90 @@
+
+#include "config.h"
+
+#include "device.h"
+
+#include <numeric>
+#include <stddef.h>
+
+#include "albit.h"
+#include "alconfig.h"
+#include "backends/base.h"
+#include "core/bformatdec.h"
+#include "core/bs2b.h"
+#include "core/front_stablizer.h"
+#include "core/hrtf.h"
+#include "core/logging.h"
+#include "core/mastering.h"
+#include "core/uhjfilter.h"
+
+
+namespace {
+
+using voidp = void*;
+
+} // namespace
+
+
+ALCdevice::ALCdevice(DeviceType type) : DeviceBase{type}
+{ }
+
+ALCdevice::~ALCdevice()
+{
+    TRACE("Freeing device %p\n", voidp{this});
+
+    Backend = nullptr;
+
+    size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u},
+        [](size_t cur, const BufferSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
+    if(count > 0)
+        WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s");
+
+    count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u},
+        [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
+    if(count > 0)
+        WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s");
+
+    count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u},
+        [](size_t cur, const FilterSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
+    if(count > 0)
+        WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s");
+}
+
+void ALCdevice::enumerateHrtfs()
+{
+    mHrtfList = EnumerateHrtf(configValue<std::string>(nullptr, "hrtf-paths"));
+    if(auto defhrtfopt = configValue<std::string>(nullptr, "default-hrtf"))
+    {
+        auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt);
+        if(iter == mHrtfList.end())
+            WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str());
+        else if(iter != mHrtfList.begin())
+            std::rotate(mHrtfList.begin(), iter, iter+1);
+    }
+}
+
+auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1
+{
+    if(mContexts.load(std::memory_order_relaxed)->empty())
+        return OutputMode1::Any;
+
+    switch(FmtChans)
+    {
+    case DevFmtMono: return OutputMode1::Mono;
+    case DevFmtStereo:
+        if(mHrtf)
+            return OutputMode1::Hrtf;
+        else if(mUhjEncoder)
+            return OutputMode1::Uhj2;
+        return OutputMode1::StereoBasic;
+    case DevFmtQuad: return OutputMode1::Quad;
+    case DevFmtX51: return OutputMode1::X51;
+    case DevFmtX61: return OutputMode1::X61;
+    case DevFmtX71: return OutputMode1::X71;
+    case DevFmtAmbi3D: break;
+    }
+    return OutputMode1::Any;
+}

+ 165 - 0
libs/openal-soft/Alc/device.h

@@ -0,0 +1,165 @@
+#ifndef ALC_DEVICE_H
+#define ALC_DEVICE_H
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "core/device.h"
+#include "inprogext.h"
+#include "intrusive_ptr.h"
+#include "vector.h"
+
+#ifdef ALSOFT_EAX
+#include "al/eax_x_ram.h"
+#endif // ALSOFT_EAX
+
+struct ALbuffer;
+struct ALeffect;
+struct ALfilter;
+struct BackendBase;
+
+using uint = unsigned int;
+
+
+struct BufferSubList {
+    uint64_t FreeMask{~0_u64};
+    ALbuffer *Buffers{nullptr}; /* 64 */
+
+    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; }
+};
+
+struct EffectSubList {
+    uint64_t FreeMask{~0_u64};
+    ALeffect *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; }
+};
+
+struct FilterSubList {
+    uint64_t FreeMask{~0_u64};
+    ALfilter *Filters{nullptr}; /* 64 */
+
+    FilterSubList() noexcept = default;
+    FilterSubList(const FilterSubList&) = delete;
+    FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
+    { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
+    ~FilterSubList();
+
+    FilterSubList& operator=(const FilterSubList&) = delete;
+    FilterSubList& operator=(FilterSubList&& rhs) noexcept
+    { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
+};
+
+
+struct ALCdevice : public al::intrusive_ref<ALCdevice>, DeviceBase {
+    /* This lock protects the device state (format, update size, etc) from
+     * being from being changed in multiple threads, or being accessed while
+     * being changed. It's also used to serialize calls to the backend.
+     */
+    std::mutex StateLock;
+    std::unique_ptr<BackendBase> Backend;
+
+    ALCuint NumMonoSources{};
+    ALCuint NumStereoSources{};
+
+    // Maximum number of sources that can be created
+    uint SourcesMax{};
+    // Maximum number of slots that can be created
+    uint AuxiliaryEffectSlotMax{};
+
+    std::string mHrtfName;
+    al::vector<std::string> mHrtfList;
+    ALCenum mHrtfStatus{ALC_FALSE};
+
+    enum class OutputMode1 : ALCenum {
+        Any = ALC_ANY_SOFT,
+        Mono = ALC_MONO_SOFT,
+        Stereo = ALC_STEREO_SOFT,
+        StereoBasic = ALC_STEREO_BASIC_SOFT,
+        Uhj2 = ALC_STEREO_UHJ_SOFT,
+        Hrtf = ALC_STEREO_HRTF_SOFT,
+        Quad = ALC_QUAD_SOFT,
+        X51 = ALC_SURROUND_5_1_SOFT,
+        X61 = ALC_SURROUND_6_1_SOFT,
+        X71 = ALC_SURROUND_7_1_SOFT
+    };
+    OutputMode1 getOutputMode1() const noexcept;
+
+    using OutputMode = OutputMode1;
+
+    std::atomic<ALCenum> LastError{ALC_NO_ERROR};
+
+    // Map of Buffers for this device
+    std::mutex BufferLock;
+    al::vector<BufferSubList> BufferList;
+
+    // Map of Effects for this device
+    std::mutex EffectLock;
+    al::vector<EffectSubList> EffectList;
+
+    // Map of Filters for this device
+    std::mutex FilterLock;
+    al::vector<FilterSubList> FilterList;
+
+#ifdef ALSOFT_EAX
+    ALuint eax_x_ram_free_size{eax_x_ram_max_size};
+#endif // ALSOFT_EAX
+
+
+    ALCdevice(DeviceType type);
+    ~ALCdevice();
+
+    void enumerateHrtfs();
+
+    bool getConfigValueBool(const char *block, const char *key, bool def)
+    { return GetConfigValueBool(DeviceName.c_str(), block, key, def); }
+
+    template<typename T>
+    al::optional<T> configValue(const char *block, const char *key) = delete;
+
+    DEF_NEWDEL(ALCdevice)
+};
+
+template<>
+inline al::optional<std::string> ALCdevice::configValue(const char *block, const char *key)
+{ return ConfigValueStr(DeviceName.c_str(), block, key); }
+template<>
+inline al::optional<int> ALCdevice::configValue(const char *block, const char *key)
+{ return ConfigValueInt(DeviceName.c_str(), block, key); }
+template<>
+inline al::optional<uint> ALCdevice::configValue(const char *block, const char *key)
+{ return ConfigValueUInt(DeviceName.c_str(), block, key); }
+template<>
+inline al::optional<float> ALCdevice::configValue(const char *block, const char *key)
+{ return ConfigValueFloat(DeviceName.c_str(), block, key); }
+template<>
+inline al::optional<bool> ALCdevice::configValue(const char *block, const char *key)
+{ return ConfigValueBool(DeviceName.c_str(), block, key); }
+
+#endif

+ 25 - 15
libs/openal-soft/Alc/effects/autowah.cpp

@@ -20,16 +20,26 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
-
 #include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <iterator>
+#include <utility>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
 
-#include "alcmain.h"
-#include "alcontext.h"
-#include "core/filters/biquad.h"
-#include "effectslot.h"
-#include "vecmat.h"
 
 namespace {
 
@@ -69,8 +79,8 @@ struct AutowahState final : public EffectState {
     alignas(16) float mBufferOut[BufferLineSize];
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -78,7 +88,7 @@ struct AutowahState final : public EffectState {
     DEF_NEWDEL(AutowahState)
 };
 
-void AutowahState::deviceUpdate(const ALCdevice*, const Buffer&)
+void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     /* (Re-)initializing parameters and clear the buffers. */
 
@@ -104,10 +114,10 @@ void AutowahState::deviceUpdate(const ALCdevice*, const Buffer&)
     }
 }
 
-void AutowahState::update(const ALCcontext *context, const EffectSlot *slot,
+void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
     const auto frequency = static_cast<float>(device->Frequency);
 
     const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
@@ -146,10 +156,10 @@ void AutowahState::process(const size_t samplesToDo,
          */
         sample = peak_gain * std::fabs(samplesIn[0][i]);
         a = (sample > env_delay) ? attack_rate : release_rate;
-        env_delay = lerp(sample, env_delay, a);
+        env_delay = lerpf(sample, env_delay, a);
 
         /* Calculate the cos and alpha components for this sample's filter. */
-        w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
+        w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v<float>*2.0f);
         mEnv[i].cos_w0 = std::cos(w0);
         mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
     }

+ 1 - 187
libs/openal-soft/Alc/effects/base.h

@@ -1,193 +1,7 @@
 #ifndef EFFECTS_BASE_H
 #define EFFECTS_BASE_H
 
-#include <cstddef>
-
-#include "albyte.h"
-#include "alcmain.h"
-#include "almalloc.h"
-#include "alspan.h"
-#include "atomic.h"
-#include "intrusive_ptr.h"
-
-struct EffectSlot;
-struct BufferStorage;
-
-
-enum class ChorusWaveform {
-    Sinusoid,
-    Triangle
-};
-
-constexpr float EchoMaxDelay{0.207f};
-constexpr float EchoMaxLRDelay{0.404f};
-
-enum class FShifterDirection {
-    Down,
-    Up,
-    Off
-};
-
-enum class ModulatorWaveform {
-    Sinusoid,
-    Sawtooth,
-    Square
-};
-
-enum class VMorpherPhenome {
-    A, E, I, O, U,
-    AA, AE, AH, AO, EH, ER, IH, IY, UH, UW,
-    B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z
-};
-
-enum class VMorpherWaveform {
-    Sinusoid,
-    Triangle,
-    Sawtooth
-};
-
-union EffectProps {
-    struct {
-        // Shared Reverb Properties
-        float Density;
-        float Diffusion;
-        float Gain;
-        float GainHF;
-        float DecayTime;
-        float DecayHFRatio;
-        float ReflectionsGain;
-        float ReflectionsDelay;
-        float LateReverbGain;
-        float LateReverbDelay;
-        float AirAbsorptionGainHF;
-        float RoomRolloffFactor;
-        bool DecayHFLimit;
-
-        // Additional EAX Reverb Properties
-        float GainLF;
-        float DecayLFRatio;
-        float ReflectionsPan[3];
-        float LateReverbPan[3];
-        float EchoTime;
-        float EchoDepth;
-        float ModulationTime;
-        float ModulationDepth;
-        float HFReference;
-        float LFReference;
-    } Reverb;
-
-    struct {
-        float AttackTime;
-        float ReleaseTime;
-        float Resonance;
-        float PeakGain;
-    } Autowah;
-
-    struct {
-        ChorusWaveform Waveform;
-        int Phase;
-        float Rate;
-        float Depth;
-        float Feedback;
-        float Delay;
-    } Chorus; /* Also Flanger */
-
-    struct {
-        bool OnOff;
-    } Compressor;
-
-    struct {
-        float Edge;
-        float Gain;
-        float LowpassCutoff;
-        float EQCenter;
-        float EQBandwidth;
-    } Distortion;
-
-    struct {
-        float Delay;
-        float LRDelay;
-
-        float Damping;
-        float Feedback;
-
-        float Spread;
-    } Echo;
-
-    struct {
-        float LowCutoff;
-        float LowGain;
-        float Mid1Center;
-        float Mid1Gain;
-        float Mid1Width;
-        float Mid2Center;
-        float Mid2Gain;
-        float Mid2Width;
-        float HighCutoff;
-        float HighGain;
-    } Equalizer;
-
-    struct {
-        float Frequency;
-        FShifterDirection LeftDirection;
-        FShifterDirection RightDirection;
-    } Fshifter;
-
-    struct {
-        float Frequency;
-        float HighPassCutoff;
-        ModulatorWaveform Waveform;
-    } Modulator;
-
-    struct {
-        int CoarseTune;
-        int FineTune;
-    } Pshifter;
-
-    struct {
-        float Rate;
-        VMorpherPhenome PhonemeA;
-        VMorpherPhenome PhonemeB;
-        int PhonemeACoarseTuning;
-        int PhonemeBCoarseTuning;
-        VMorpherWaveform Waveform;
-    } Vmorpher;
-
-    struct {
-        float Gain;
-    } Dedicated;
-};
-
-
-struct EffectTarget {
-    MixParams *Main;
-    RealMixParams *RealOut;
-};
-
-struct EffectState : public al::intrusive_ref<EffectState> {
-    struct Buffer {
-        const BufferStorage *storage;
-        al::span<const al::byte> samples;
-    };
-
-    al::span<FloatBufferLine> mOutTarget;
-
-
-    virtual ~EffectState() = default;
-
-    virtual void deviceUpdate(const ALCdevice *device, const Buffer &buffer) = 0;
-    virtual void update(const ALCcontext *context, const EffectSlot *slot,
-        const EffectProps *props, const EffectTarget target) = 0;
-    virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
-        const al::span<FloatBufferLine> samplesOut) = 0;
-};
-
-
-struct EffectStateFactory {
-    virtual ~EffectStateFactory() = default;
-
-    virtual al::intrusive_ptr<EffectState> create() = 0;
-};
+#include "core/effects/base.h"
 
 
 EffectStateFactory *NullStateFactory_getFactory(void);

+ 22 - 16
libs/openal-soft/Alc/effects/chorus.cpp

@@ -21,27 +21,33 @@
 #include "config.h"
 
 #include <algorithm>
+#include <array>
 #include <climits>
-#include <cmath>
 #include <cstdlib>
 #include <iterator>
 
-#include "alcmain.h"
-#include "alcontext.h"
+#include "alc/effects/base.h"
 #include "almalloc.h"
+#include "alnumbers.h"
 #include "alnumeric.h"
 #include "alspan.h"
-#include "alu.h"
-#include "core/ambidefs.h"
-#include "effects/base.h"
-#include "effectslot.h"
-#include "math_defs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "core/resampler_limits.h"
+#include "intrusive_ptr.h"
 #include "opthelpers.h"
 #include "vector.h"
 
 
 namespace {
 
+using uint = unsigned int;
+
 #define MAX_UPDATE_SAMPLES 256
 
 struct ChorusState final : public EffectState {
@@ -68,8 +74,8 @@ struct ChorusState final : public EffectState {
     void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
     void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -77,9 +83,9 @@ struct ChorusState final : public EffectState {
     DEF_NEWDEL(ChorusState)
 };
 
-void ChorusState::deviceUpdate(const ALCdevice *Device, const Buffer&)
+void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&)
 {
-    constexpr float max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
+    constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
 
     const auto frequency = static_cast<float>(Device->Frequency);
     const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
@@ -94,7 +100,7 @@ void ChorusState::deviceUpdate(const ALCdevice *Device, const Buffer&)
     }
 }
 
-void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot,
+void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
     const EffectProps *props, const EffectTarget target)
 {
     constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
@@ -102,7 +108,7 @@ void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot,
     /* The LFO depth is scaled to be relative to the sample delay. Clamp the
      * delay and depth to allow enough padding for resampling.
      */
-    const ALCdevice *device{Context->mDevice.get()};
+    const DeviceBase *device{Context->mDevice};
     const auto frequency = static_cast<float>(device->Frequency);
 
     mWaveform = props->Chorus.Waveform;
@@ -144,7 +150,7 @@ void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot,
             mLfoScale = 4.0f / static_cast<float>(mLfoRange);
             break;
         case ChorusWaveform::Sinusoid:
-            mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
+            mLfoScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mLfoRange);
             break;
         }
 
@@ -247,7 +253,7 @@ void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBu
             ++offset;
         }
 
-        for(ALsizei c{0};c < 2;++c)
+        for(size_t c{0};c < 2;++c)
             MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
                 samplesToDo-base, base);
 

+ 47 - 23
libs/openal-soft/Alc/effects/compressor.cpp

@@ -1,32 +1,56 @@
 /**
- * OpenAL cross platform audio library
+ * This file is part of the OpenAL Soft cross platform audio library
+ *
  * Copyright (C) 2013 by Anis A. Hireche
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
  *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
  *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
+ * * Neither the name of Spherical-Harmonic-Transform nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "config.h"
 
+#include <array>
 #include <cstdlib>
+#include <iterator>
+#include <utility>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "intrusive_ptr.h"
 
-#include "alcmain.h"
-#include "alcontext.h"
-#include "alu.h"
-#include "effectslot.h"
-#include "vecmat.h"
+struct ContextBase;
 
 
 namespace {
@@ -49,8 +73,8 @@ struct CompressorState final : public EffectState {
     float mEnvFollower{1.0f};
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -58,7 +82,7 @@ struct CompressorState final : public EffectState {
     DEF_NEWDEL(CompressorState)
 };
 
-void CompressorState::deviceUpdate(const ALCdevice *device, const Buffer&)
+void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&)
 {
     /* Number of samples to do a full attack and release (non-integer sample
      * counts are okay).
@@ -73,7 +97,7 @@ void CompressorState::deviceUpdate(const ALCdevice *device, const Buffer&)
     mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
 }
 
-void CompressorState::update(const ALCcontext*, const EffectSlot *slot,
+void CompressorState::update(const ContextBase*, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
     mEnabled = props->Compressor.OnOff;

+ 66 - 21
libs/openal-soft/Alc/effects/convolution.cpp

@@ -1,7 +1,15 @@
 
 #include "config.h"
 
+#include <algorithm>
+#include <array>
+#include <complex>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <memory>
 #include <stdint.h>
+#include <utility>
 
 #ifdef HAVE_SSE_INTRINSICS
 #include <xmmintrin.h>
@@ -9,21 +17,26 @@
 #include <arm_neon.h>
 #endif
 
-#include "alcmain.h"
+#include "albyte.h"
 #include "alcomplex.h"
-#include "alcontext.h"
 #include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
 #include "alspan.h"
-#include "bformatdec.h"
-#include "buffer_storage.h"
+#include "base.h"
 #include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/buffer_storage.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/splitter.h"
 #include "core/fmt_traits.h"
-#include "core/logging.h"
-#include "effects/base.h"
-#include "effectslot.h"
-#include "math_defs.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
 #include "polyphase_resampler.h"
+#include "vector.h"
 
 
 namespace {
@@ -78,8 +91,13 @@ void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep
 
 inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
 {
-    if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa();
-    if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D();
+    switch(scaletype)
+    {
+    case AmbiScaling::FuMa: return AmbiScale::FromFuMa();
+    case AmbiScaling::SN3D: return AmbiScale::FromSN3D();
+    case AmbiScaling::UHJ: return AmbiScale::FromUHJ();
+    case AmbiScaling::N3D: break;
+    }
     return AmbiScale::FromN3D();
 }
 
@@ -102,6 +120,10 @@ struct ChanMap {
     float elevation;
 };
 
+constexpr float Deg2Rad(float x) noexcept
+{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
+
+
 using complex_d = std::complex<double>;
 
 constexpr size_t ConvolveUpdateSize{256};
@@ -190,8 +212,8 @@ struct ConvolutionState final : public EffectState {
     void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
     {&ConvolutionState::NormalMix};
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -219,7 +241,7 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
 }
 
 
-void ConvolutionState::deviceUpdate(const ALCdevice *device, const Buffer &buffer)
+void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer)
 {
     constexpr uint MaxConvolveAmbiOrder{1u};
 
@@ -316,7 +338,7 @@ void ConvolutionState::deviceUpdate(const ALCdevice *device, const Buffer &buffe
 }
 
 
-void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
+void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps* /*props*/, const EffectTarget target)
 {
     /* NOTE: Stereo and Rear are slightly different from normal mixing (as
@@ -327,7 +349,7 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
      * to have its own output target since the main mixing buffer won't have an
      * LFE channel (due to being B-Format).
      */
-    static const ChanMap MonoMap[1]{
+    static constexpr ChanMap MonoMap[1]{
         { FrontCenter, 0.0f, 0.0f }
     }, StereoMap[2]{
         { FrontLeft,  Deg2Rad(-45.0f), Deg2Rad(0.0f) },
@@ -374,13 +396,31 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
     for(auto &chan : *mChans)
         std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
     const float gain{slot->Gain};
-    if(mChannels == FmtBFormat3D || mChannels == FmtBFormat2D)
+    /* TODO: UHJ should be decoded to B-Format and processed that way, since
+     * there's no telling if it can ever do a direct-out mix (even if the
+     * device is outputing UHJ, the effect slot can feed another effect that's
+     * not UHJ).
+     *
+     * Not that UHJ should really ever be used for convolution, but it's a
+     * valid format regardless.
+     */
+    if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut
+        && target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX
+        && target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX)
+    {
+        mOutTarget = target.RealOut->Buffer;
+        const uint lidx = target.RealOut->ChannelIndex[FrontLeft];
+        const uint ridx = target.RealOut->ChannelIndex[FrontRight];
+        (*mChans)[0].Target[lidx] = gain;
+        (*mChans)[1].Target[ridx] = gain;
+    }
+    else if(IsBFormat(mChannels))
     {
-        ALCdevice *device{context->mDevice.get()};
+        DeviceBase *device{context->mDevice};
         if(device->mAmbiOrder > mAmbiOrder)
         {
             mMix = &ConvolutionState::UpsampleMix;
-            const auto scales = BFormatDec::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
+            const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
             (*mChans)[0].mHfScale = scales[0];
             for(size_t i{1};i < mChans->size();++i)
                 (*mChans)[i].mHfScale = scales[1];
@@ -403,11 +443,12 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
     }
     else
     {
-        ALCdevice *device{context->mDevice.get()};
+        DeviceBase *device{context->mDevice};
         al::span<const ChanMap> chanmap{};
         switch(mChannels)
         {
         case FmtMono: chanmap = MonoMap; break;
+        case FmtSuperStereo:
         case FmtStereo: chanmap = StereoMap; break;
         case FmtRear: chanmap = RearMap; break;
         case FmtQuad: chanmap = QuadMap; break;
@@ -416,6 +457,9 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
         case FmtX71: chanmap = X71Map; break;
         case FmtBFormat2D:
         case FmtBFormat3D:
+        case FmtUHJ2:
+        case FmtUHJ3:
+        case FmtUHJ4:
             break;
         }
 
@@ -424,9 +468,10 @@ void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
         {
             auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
             {
+                constexpr float half_pi{al::numbers::pi_v<float>*0.5f};
                 const float abs_azi{std::fabs(azimuth)};
-                if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
-                    return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
+                if(!(abs_azi >= half_pi))
+                    return std::copysign(minf(abs_azi*scale, half_pi), azimuth);
                 return azimuth;
             };
 

+ 20 - 10
libs/openal-soft/Alc/effects/dedicated.cpp

@@ -20,25 +20,35 @@
 
 #include "config.h"
 
-#include <cstdlib>
-#include <cmath>
 #include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <iterator>
 
-#include "alcmain.h"
-#include "alcontext.h"
-#include "alu.h"
-#include "effectslot.h"
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "core/bufferline.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
+
+struct ContextBase;
 
 
 namespace {
 
+using uint = unsigned int;
+
 struct DedicatedState final : public EffectState {
     float mCurrentGains[MAX_OUTPUT_CHANNELS];
     float mTargetGains[MAX_OUTPUT_CHANNELS];
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -46,12 +56,12 @@ struct DedicatedState final : public EffectState {
     DEF_NEWDEL(DedicatedState)
 };
 
-void DedicatedState::deviceUpdate(const ALCdevice*, const Buffer&)
+void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
 }
 
-void DedicatedState::update(const ALCcontext*, const EffectSlot *slot,
+void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
     std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);

+ 22 - 11
libs/openal-soft/Alc/effects/distortion.cpp

@@ -21,13 +21,24 @@
 #include "config.h"
 
 #include <algorithm>
-#include <cmath>
+#include <array>
 #include <cstdlib>
-
-#include "alcmain.h"
-#include "alcontext.h"
+#include <iterator>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/biquad.h"
-#include "effectslot.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "intrusive_ptr.h"
 
 
 namespace {
@@ -45,8 +56,8 @@ struct DistortionState final : public EffectState {
     float mBuffer[2][BufferLineSize]{};
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -54,19 +65,19 @@ struct DistortionState final : public EffectState {
     DEF_NEWDEL(DistortionState)
 };
 
-void DistortionState::deviceUpdate(const ALCdevice*, const Buffer&)
+void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     mLowpass.clear();
     mBandpass.clear();
 }
 
-void DistortionState::update(const ALCcontext *context, const EffectSlot *slot,
+void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
 
     /* Store waveshaper edge settings. */
-    const float edge{minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge),
+    const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge),
         0.99f)};
     mEdgeCoeff = 2.0f * edge / (1.0f-edge);
 

+ 25 - 13
libs/openal-soft/Alc/effects/echo.cpp

@@ -20,20 +20,32 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
-
 #include <algorithm>
-
-#include "alcmain.h"
-#include "alcontext.h"
+#include <array>
+#include <cstdlib>
+#include <iterator>
+#include <tuple>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/biquad.h"
-#include "effectslot.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
+#include "opthelpers.h"
 #include "vector.h"
 
 
 namespace {
 
+using uint = unsigned int;
+
 constexpr float LowpassFreqRef{5000.0f};
 
 struct EchoState final : public EffectState {
@@ -57,8 +69,8 @@ struct EchoState final : public EffectState {
 
     alignas(16) float mTempBuffer[2][BufferLineSize];
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -66,7 +78,7 @@ struct EchoState final : public EffectState {
     DEF_NEWDEL(EchoState)
 };
 
-void EchoState::deviceUpdate(const ALCdevice *Device, const Buffer&)
+void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&)
 {
     const auto frequency = static_cast<float>(Device->Frequency);
 
@@ -85,10 +97,10 @@ void EchoState::deviceUpdate(const ALCdevice *Device, const Buffer&)
     }
 }
 
-void EchoState::update(const ALCcontext *context, const EffectSlot *slot,
+void EchoState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
     const auto frequency = static_cast<float>(device->Frequency);
 
     mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
@@ -148,7 +160,7 @@ void EchoState::process(const size_t samplesToDo, const al::span<const FloatBuff
     mFilter.setComponents(z1, z2);
     mOffset = offset;
 
-    for(ALsizei c{0};c < 2;c++)
+    for(size_t c{0};c < 2;c++)
         MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
             samplesToDo, 0);
 }

+ 21 - 13
libs/openal-soft/Alc/effects/equalizer.cpp

@@ -20,17 +20,25 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
-
 #include <algorithm>
+#include <array>
+#include <cstdlib>
 #include <functional>
-
-#include "alcmain.h"
-#include "alcontext.h"
+#include <iterator>
+#include <utility>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/biquad.h"
-#include "effectslot.h"
-#include "vecmat.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
 
 
 namespace {
@@ -90,8 +98,8 @@ struct EqualizerState final : public EffectState {
     FloatBufferLine mSampleBuffer{};
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -99,7 +107,7 @@ struct EqualizerState final : public EffectState {
     DEF_NEWDEL(EqualizerState)
 };
 
-void EqualizerState::deviceUpdate(const ALCdevice*, const Buffer&)
+void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     for(auto &e : mChans)
     {
@@ -108,10 +116,10 @@ void EqualizerState::deviceUpdate(const ALCdevice*, const Buffer&)
     }
 }
 
-void EqualizerState::update(const ALCcontext *context, const EffectSlot *slot,
+void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
     auto frequency = static_cast<float>(device->Frequency);
     float gain, f0norm;
 

+ 25 - 15
libs/openal-soft/Alc/effects/fshifter.cpp

@@ -20,22 +20,32 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
+#include <algorithm>
 #include <array>
+#include <cmath>
 #include <complex>
-#include <algorithm>
+#include <cstdlib>
+#include <iterator>
 
-#include "alcmain.h"
+#include "alc/effects/base.h"
 #include "alcomplex.h"
-#include "alcontext.h"
-#include "alu.h"
-#include "effectslot.h"
-#include "math_defs.h"
+#include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "intrusive_ptr.h"
 
 
 namespace {
 
+using uint = unsigned int;
 using complex_d = std::complex<double>;
 
 #define HIL_SIZE 1024
@@ -51,7 +61,7 @@ std::array<double,HIL_SIZE> InitHannWindow()
     /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
     for(size_t i{0};i < HIL_SIZE>>1;i++)
     {
-        constexpr double scale{al::MathDefs<double>::Pi() / double{HIL_SIZE}};
+        constexpr double scale{al::numbers::pi / double{HIL_SIZE}};
         const double val{std::sin(static_cast<double>(i+1) * scale)};
         ret[i] = ret[HIL_SIZE-1-i] = val * val;
     }
@@ -84,8 +94,8 @@ struct FshifterState final : public EffectState {
     } mGains[2];
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -93,7 +103,7 @@ struct FshifterState final : public EffectState {
     DEF_NEWDEL(FshifterState)
 };
 
-void FshifterState::deviceUpdate(const ALCdevice*, const Buffer&)
+void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     /* (Re-)initializing parameters and clear the buffers. */
     mCount = 0;
@@ -114,10 +124,10 @@ void FshifterState::deviceUpdate(const ALCdevice*, const Buffer&)
     }
 }
 
-void FshifterState::update(const ALCcontext *context, const EffectSlot *slot,
+void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
 
     const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)};
     mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne);
@@ -207,7 +217,7 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
         uint phase_idx{mPhase[c]};
         for(size_t k{0};k < samplesToDo;++k)
         {
-            const double phase{phase_idx * ((1.0/MixerFracOne) * al::MathDefs<double>::Tau())};
+            const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
             BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
                 mOutdata[k].imag()*std::sin(phase)*mSign[c]);
 

+ 25 - 15
libs/openal-soft/Alc/effects/modulator.cpp

@@ -20,21 +20,31 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
-
-#include <cmath>
 #include <algorithm>
-
-#include "alcmain.h"
-#include "alcontext.h"
+#include <array>
+#include <cstdlib>
+#include <iterator>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/biquad.h"
-#include "effectslot.h"
-#include "vecmat.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
 
 
 namespace {
 
+using uint = unsigned int;
+
 #define MAX_UPDATE_SAMPLES 128
 
 #define WAVEFORM_FRACBITS  24
@@ -43,7 +53,7 @@ namespace {
 
 inline float Sin(uint index)
 {
-    constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
+    constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
     return std::sin(static_cast<float>(index) * scale);
 }
 
@@ -81,8 +91,8 @@ struct ModulatorState final : public EffectState {
     } mChans[MaxAmbiChannels];
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -90,7 +100,7 @@ struct ModulatorState final : public EffectState {
     DEF_NEWDEL(ModulatorState)
 };
 
-void ModulatorState::deviceUpdate(const ALCdevice*, const Buffer&)
+void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     for(auto &e : mChans)
     {
@@ -99,10 +109,10 @@ void ModulatorState::deviceUpdate(const ALCdevice*, const Buffer&)
     }
 }
 
-void ModulatorState::update(const ALCcontext *context, const EffectSlot *slot,
+void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
 
     const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)};
     mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));

+ 13 - 8
libs/openal-soft/Alc/effects/null.cpp

@@ -1,12 +1,17 @@
 
 #include "config.h"
 
-#include "alcmain.h"
-#include "alcontext.h"
+#include <stddef.h>
+
 #include "almalloc.h"
 #include "alspan.h"
-#include "effects/base.h"
-#include "effectslot.h"
+#include "base.h"
+#include "core/bufferline.h"
+#include "intrusive_ptr.h"
+
+struct ContextBase;
+struct DeviceBase;
+struct EffectSlot;
 
 
 namespace {
@@ -15,8 +20,8 @@ struct NullState final : public EffectState {
     NullState();
     ~NullState() override;
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -39,14 +44,14 @@ NullState::~NullState() = default;
  * format) have been changed. Will always be followed by a call to the update
  * method, if successful.
  */
-void NullState::deviceUpdate(const ALCdevice* /*device*/, const Buffer& /*buffer*/)
+void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/)
 {
 }
 
 /* This updates the effect state with new properties. This is called any time
  * the effect is (re)loaded into a slot.
  */
-void NullState::update(const ALCcontext* /*context*/, const EffectSlot* /*slot*/,
+void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/,
     const EffectProps* /*props*/, const EffectTarget /*target*/)
 {
 }

+ 26 - 16
libs/openal-soft/Alc/effects/pshifter.cpp

@@ -20,23 +20,33 @@
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
+#include <algorithm>
 #include <array>
+#include <cmath>
 #include <complex>
-#include <algorithm>
+#include <cstdlib>
+#include <iterator>
 
-#include "alcmain.h"
+#include "alc/effects/base.h"
 #include "alcomplex.h"
-#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumbers.h"
 #include "alnumeric.h"
-#include "alu.h"
-#include "effectslot.h"
-#include "math_defs.h"
+#include "alspan.h"
+#include "core/bufferline.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "intrusive_ptr.h"
+
+struct ContextBase;
 
 
 namespace {
 
+using uint = unsigned int;
 using complex_d = std::complex<double>;
 
 #define STFT_SIZE      1024
@@ -53,7 +63,7 @@ std::array<double,STFT_SIZE> InitHannWindow()
     /* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
     for(size_t i{0};i < STFT_SIZE>>1;i++)
     {
-        constexpr double scale{al::MathDefs<double>::Pi() / double{STFT_SIZE}};
+        constexpr double scale{al::numbers::pi / double{STFT_SIZE}};
         const double val{std::sin(static_cast<double>(i+1) * scale)};
         ret[i] = ret[STFT_SIZE-1-i] = val * val;
     }
@@ -93,8 +103,8 @@ struct PshifterState final : public EffectState {
     float mTargetGains[MAX_OUTPUT_CHANNELS];
 
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -102,7 +112,7 @@ struct PshifterState final : public EffectState {
     DEF_NEWDEL(PshifterState)
 };
 
-void PshifterState::deviceUpdate(const ALCdevice*, const Buffer&)
+void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     /* (Re-)initializing parameters and clear the buffers. */
     mCount       = 0;
@@ -122,7 +132,7 @@ void PshifterState::deviceUpdate(const ALCdevice*, const Buffer&)
     std::fill(std::begin(mTargetGains),  std::end(mTargetGains),  0.0f);
 }
 
-void PshifterState::update(const ALCcontext*, const EffectSlot *slot,
+void PshifterState::update(const ContextBase*, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
     const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
@@ -145,7 +155,7 @@ void PshifterState::process(const size_t samplesToDo, const al::span<const Float
     /* Cycle offset per update expected of each frequency bin (bin 0 is none,
      * bin 1 is x1, bin 2 is x2, etc).
      */
-    constexpr double expected_cycles{al::MathDefs<double>::Tau() / OVERSAMP};
+    constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP};
 
     for(size_t base{0u};base < samplesToDo;)
     {
@@ -188,8 +198,8 @@ void PshifterState::process(const size_t samplesToDo, const al::span<const Float
             double tmp{(phase - mLastPhase[k]) - static_cast<double>(k)*expected_cycles};
 
             /* Map delta phase into +/- Pi interval */
-            int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
-            tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
+            int qpd{double2int(tmp / al::numbers::pi)};
+            tmp -= al::numbers::pi * (qpd + (qpd%2));
 
             /* Get deviation from bin frequency from the +/- Pi interval */
             tmp /= expected_cycles;

+ 95 - 76
libs/openal-soft/Alc/effects/reverb.cpp

@@ -20,23 +20,33 @@
 
 #include "config.h"
 
-#include <cstdio>
-#include <cstdlib>
-#include <cmath>
-
-#include <array>
-#include <numeric>
 #include <algorithm>
+#include <array>
+#include <cstdio>
 #include <functional>
+#include <iterator>
+#include <numeric>
+#include <stdint.h>
 
-#include "alcmain.h"
-#include "alcontext.h"
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumbers.h"
 #include "alnumeric.h"
-#include "bformatdec.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
 #include "core/filters/biquad.h"
-#include "effectslot.h"
-#include "vector.h"
+#include "core/filters/splitter.h"
+#include "core/mixer.h"
+#include "core/mixer/defs.h"
+#include "intrusive_ptr.h"
+#include "opthelpers.h"
 #include "vecmat.h"
+#include "vector.h"
 
 /* This is a user config option for modifying the overall output of the reverb
  * effect.
@@ -45,6 +55,11 @@ float ReverbBoost = 1.0f;
 
 namespace {
 
+using uint = unsigned int;
+
+constexpr float MaxModulationTime{4.0f};
+constexpr float DefaultModulationTime{0.25f};
+
 #define MOD_FRACBITS 24
 #define MOD_FRACONE  (1<<MOD_FRACBITS)
 #define MOD_FRACMASK (MOD_FRACONE-1)
@@ -82,20 +97,28 @@ constexpr float MODULATION_DEPTH_COEFF{0.05f};
  * in the future, true opposites can be used.
  */
 alignas(16) constexpr float B2A[NUM_LINES][NUM_LINES]{
-    { 0.288675134595f,  0.288675134595f,  0.288675134595f,  0.288675134595f },
-    { 0.288675134595f, -0.288675134595f, -0.288675134595f,  0.288675134595f },
-    { 0.288675134595f,  0.288675134595f, -0.288675134595f, -0.288675134595f },
-    { 0.288675134595f, -0.288675134595f,  0.288675134595f, -0.288675134595f }
+    { 0.5f,  0.5f,  0.5f,  0.5f },
+    { 0.5f, -0.5f, -0.5f,  0.5f },
+    { 0.5f,  0.5f, -0.5f, -0.5f },
+    { 0.5f, -0.5f,  0.5f, -0.5f }
 };
 
-/* Converts A-Format to B-Format. */
-alignas(16) constexpr float A2B[NUM_LINES][NUM_LINES]{
-    { 0.866025403785f,  0.866025403785f,  0.866025403785f,  0.866025403785f },
-    { 0.866025403785f, -0.866025403785f,  0.866025403785f, -0.866025403785f },
-    { 0.866025403785f, -0.866025403785f, -0.866025403785f,  0.866025403785f },
-    { 0.866025403785f,  0.866025403785f, -0.866025403785f, -0.866025403785f }
+/* Converts A-Format to B-Format for early reflections. */
+alignas(16) constexpr float EarlyA2B[NUM_LINES][NUM_LINES]{
+    { 0.5f,  0.5f,  0.5f,  0.5f },
+    { 0.5f, -0.5f,  0.5f, -0.5f },
+    { 0.5f, -0.5f, -0.5f,  0.5f },
+    { 0.5f,  0.5f, -0.5f, -0.5f }
 };
 
+/* Converts A-Format to B-Format for late reverb. */
+constexpr auto InvSqrt2 = static_cast<float>(1.0/al::numbers::sqrt2);
+alignas(16) constexpr float LateA2B[NUM_LINES][NUM_LINES]{
+    { 0.5f,  0.5f,  0.5f,  0.5f },
+    { InvSqrt2, -InvSqrt2,  0.0f,  0.0f },
+    { 0.0f,  0.0f,  InvSqrt2, -InvSqrt2 },
+    { 0.5f,  0.5f, -0.5f, -0.5f }
+};
 
 /* The all-pass and delay lines have a variable length dependent on the
  * effect's density parameter, which helps alter the perceived environment
@@ -379,15 +402,15 @@ struct ReverbState final : public EffectState {
         /* Calculated parameters which indicate if cross-fading is needed after
          * an update.
          */
-        float Density{AL_EAXREVERB_DEFAULT_DENSITY};
-        float Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION};
-        float DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME};
-        float HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
-        float LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
-        float ModulationTime{AL_EAXREVERB_DEFAULT_MODULATION_TIME};
-        float ModulationDepth{AL_EAXREVERB_DEFAULT_MODULATION_DEPTH};
-        float HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE};
-        float LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE};
+        float Density{1.0f};
+        float Diffusion{1.0f};
+        float DecayTime{1.49f};
+        float HFDecayTime{0.83f * 1.49f};
+        float LFDecayTime{1.0f * 1.49f};
+        float ModulationTime{0.25f};
+        float ModulationDepth{0.0f};
+        float HFReference{5000.0f};
+        float LFReference{250.0f};
     } mParams;
 
     /* Master effect filters */
@@ -469,13 +492,13 @@ struct ReverbState final : public EffectState {
         const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), todo};
         for(size_t c{0u};c < NUM_LINES;c++)
         {
-            DoMixRow(tmpspan, A2B[c], mEarlySamples[0].data(), mEarlySamples[0].size());
+            DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size());
             MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter,
                 offset);
         }
         for(size_t c{0u};c < NUM_LINES;c++)
         {
-            DoMixRow(tmpspan, A2B[c], mLateSamples[0].data(), mLateSamples[0].size());
+            DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size());
             MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter,
                 offset);
         }
@@ -489,7 +512,7 @@ struct ReverbState final : public EffectState {
         const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), todo};
         for(size_t c{0u};c < NUM_LINES;c++)
         {
-            DoMixRow(tmpspan, A2B[c], mEarlySamples[0].data(), mEarlySamples[0].size());
+            DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size());
 
             /* Apply scaling to the B-Format's HF response to "upsample" it to
              * higher-order output.
@@ -502,7 +525,7 @@ struct ReverbState final : public EffectState {
         }
         for(size_t c{0u};c < NUM_LINES;c++)
         {
-            DoMixRow(tmpspan, A2B[c], mLateSamples[0].data(), mLateSamples[0].size());
+            DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size());
 
             const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
             mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale);
@@ -527,8 +550,8 @@ struct ReverbState final : public EffectState {
     void lateFaded(const size_t offset, const size_t todo, const float fade,
         const float fadeStep);
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -556,16 +579,17 @@ void ReverbState::allocLines(const float frequency)
     /* Multiplier for the maximum density value, i.e. density=1, which is
      * actually the least density...
      */
-    const float multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+    const float multiplier{CalcDelayLengthMult(1.0f)};
 
     /* The main delay length includes the maximum early reflection delay, the
      * largest early tap width, the maximum late reverb delay, and the
      * largest late tap width.  Finally, it must also be extended by the
      * update size (BufferLineSize) for block processing.
      */
-    float length{AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier +
-        AL_EAXREVERB_MAX_LATE_REVERB_DELAY +
-        (LATE_LINE_LENGTHS.back() - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*multiplier};
+    constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) /
+        float{NUM_LINES}};
+    float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier +
+        ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier};
     totalSamples += mDelay.calcLineLength(length, totalSamples, frequency, BufferLineSize);
 
     /* The early vector all-pass line. */
@@ -584,7 +608,7 @@ void ReverbState::allocLines(const float frequency)
      * time and depth coefficient, and halfed for the low-to-high frequency
      * swing.
      */
-    constexpr float max_mod_delay{AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF / 2.0f};
+    constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f};
 
     /* The late delay lines are calculated from the largest maximum density
      * line length, and the maximum modulation delay. An additional sample is
@@ -607,18 +631,18 @@ void ReverbState::allocLines(const float frequency)
     mLate.Delay.realizeLineOffset(mSampleBuffer.data());
 }
 
-void ReverbState::deviceUpdate(const ALCdevice *device, const Buffer&)
+void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&)
 {
     const auto frequency = static_cast<float>(device->Frequency);
 
     /* Allocate the delay lines. */
     allocLines(frequency);
 
-    const float multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+    const float multiplier{CalcDelayLengthMult(1.0f)};
 
     /* The late feed taps are set a fixed position past the latest delay tap. */
-    mLateFeedTap = float2uint(
-        (AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier) * frequency);
+    mLateFeedTap = float2uint((ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier) *
+        frequency);
 
     /* Clear filters and gain coefficients since the delay lines were all just
      * cleared (if not reallocated).
@@ -665,7 +689,7 @@ void ReverbState::deviceUpdate(const ALCdevice *device, const Buffer&)
     if(device->mAmbiOrder > 1)
     {
         mMixOut = &ReverbState::MixOutAmbiUp;
-        mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder);
+        mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder);
     }
     else
     {
@@ -721,7 +745,7 @@ inline float CalcDensityGain(const float a)
 inline void CalcMatrixCoeffs(const float diffusion, float *x, float *y)
 {
     /* The matrix is of order 4, so n is sqrt(4 - 1). */
-    constexpr float n{1.73205080756887719318f/*std::sqrt(3.0f)*/};
+    constexpr float n{al::numbers::sqrt3_v<float>};
     const float t{diffusion * std::atan(n)};
 
     /* Calculate the first mixing matrix coefficient. */
@@ -770,10 +794,8 @@ void T60Filter::calcCoeffs(const float length, const float lfDecayTime,
 void EarlyReflections::updateLines(const float density_mult, const float diffusion,
     const float decayTime, const float frequency)
 {
-    constexpr float sqrt1_2{0.70710678118654752440f/*1.0f/std::sqrt(2.0f)*/};
-
     /* Calculate the all-pass feed-back/forward coefficient. */
-    VecAp.Coeff = diffusion*diffusion * sqrt1_2;
+    VecAp.Coeff = diffusion*diffusion * InvSqrt2;
 
     for(size_t i{0u};i < NUM_LINES;i++)
     {
@@ -813,15 +835,14 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency)
      * (half of it is spent decreasing the frequency, half is spent increasing
      * it).
      */
-    if(modTime >= AL_EAXREVERB_DEFAULT_MODULATION_TIME)
+    if(modTime >= DefaultModulationTime)
     {
         /* To cancel the effects of a long period modulation on the late
          * reverberation, the amount of pitch should be varied (decreased)
          * according to the modulation time. The natural form is varying
          * inversely, in fact resulting in an invariant.
          */
-        Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * AL_EAXREVERB_DEFAULT_MODULATION_TIME *
-            modDepth * frequency;
+        Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency;
     }
     else
         Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency;
@@ -835,7 +856,8 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
     /* Scaling factor to convert the normalized reference frequencies from
      * representing 0...freq to 0...max_reference.
      */
-    const float norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE};
+    constexpr float MaxHFReference{20000.0f};
+    const float norm_weight_factor{frequency / MaxHFReference};
 
     const float late_allpass_avg{
         std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) /
@@ -863,8 +885,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
     DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted));
 
     /* Calculate the all-pass feed-back/forward coefficient. */
-    constexpr float sqrt1_2{0.70710678118654752440f/*1.0f/std::sqrt(2.0f)*/};
-    VecAp.Coeff = diffusion*diffusion * sqrt1_2;
+    VecAp.Coeff = diffusion*diffusion * InvSqrt2;
 
     for(size_t i{0u};i < NUM_LINES;i++)
     {
@@ -881,7 +902,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
          * filter for each of its four lines. Also include the average
          * modulation delay (depth is half the max delay in samples).
          */
-        length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult +
+        length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult +
             Mod.Depth[1]/frequency;
 
         /* Calculate the T60 damping coefficients for each line. */
@@ -923,8 +944,6 @@ void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay,
  */
 alu::Matrix GetTransformFromVector(const float *vec)
 {
-    constexpr float sqrt3{1.73205080756887719318f};
-
     /* Normalize the panning vector according to the N3D scale, which has an
      * extra sqrt(3) term on the directional components. Converting from OpenAL
      * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however
@@ -936,9 +955,9 @@ alu::Matrix GetTransformFromVector(const float *vec)
     float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])};
     if(mag > 1.0f)
     {
-        norm[0] = vec[0] / mag * -sqrt3;
-        norm[1] = vec[1] / mag * sqrt3;
-        norm[2] = vec[2] / mag * sqrt3;
+        norm[0] = vec[0] / mag * -al::numbers::sqrt3_v<float>;
+        norm[1] = vec[1] / mag * al::numbers::sqrt3_v<float>;
+        norm[2] = vec[2] / mag * al::numbers::sqrt3_v<float>;
         mag = 1.0f;
     }
     else
@@ -947,9 +966,9 @@ alu::Matrix GetTransformFromVector(const float *vec)
          * term. There's no need to renormalize the magnitude since it would
          * just be reapplied in the matrix.
          */
-        norm[0] = vec[0] * -sqrt3;
-        norm[1] = vec[1] * sqrt3;
-        norm[2] = vec[2] * sqrt3;
+        norm[0] = vec[0] * -al::numbers::sqrt3_v<float>;
+        norm[1] = vec[1] * al::numbers::sqrt3_v<float>;
+        norm[2] = vec[2] * al::numbers::sqrt3_v<float>;
     }
 
     return alu::Matrix{
@@ -985,10 +1004,10 @@ void ReverbState::update3DPanning(const float *ReflectionsPan, const float *Late
     }
 }
 
-void ReverbState::update(const ALCcontext *Context, const EffectSlot *Slot,
+void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *Device{Context->mDevice.get()};
+    const DeviceBase *Device{Context->mDevice};
     const auto frequency = static_cast<float>(Device->Frequency);
 
     /* Calculate the master filters */
@@ -1024,10 +1043,10 @@ void ReverbState::update(const ALCcontext *Context, const EffectSlot *Slot,
             props->Reverb.DecayTime);
 
     /* Calculate the LF/HF decay times. */
-    const float lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio,
-        AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
-    const float hfDecayTime{clampf(props->Reverb.DecayTime * hfRatio,
-        AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
+    constexpr float MinDecayTime{0.1f}, MaxDecayTime{20.0f};
+    const float lfDecayTime{clampf(props->Reverb.DecayTime*props->Reverb.DecayLFRatio,
+        MinDecayTime, MaxDecayTime)};
+    const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)};
 
     /* Update the modulator rate and depth. */
     mLate.Mod.updateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth,
@@ -1408,7 +1427,7 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const float
 
 void Modulation::calcDelays(size_t todo)
 {
-    constexpr float inv_scale{MOD_FRACONE / al::MathDefs<float>::Tau()};
+    constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v<float> / 2.0f};
     uint idx{Index};
     const uint step{Step};
     const float depth{Depth[0]};
@@ -1423,7 +1442,7 @@ void Modulation::calcDelays(size_t todo)
 
 void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep)
 {
-    constexpr float inv_scale{MOD_FRACONE / al::MathDefs<float>::Tau()};
+    constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v<float> / 2.0f};
     uint idx{Index};
     const uint step{Step};
     const float depth{Depth[0]};
@@ -1498,7 +1517,7 @@ void ReverbState::lateUnfaded(const size_t offset, const size_t todo)
                  * samples that were acquired above, and combined with the main
                  * delay tap.
                  */
-                mTempSamples[j][i] = lerp(out0, out1, frac)*midGain +
+                mTempSamples[j][i] = lerpf(out0, out1, frac)*midGain +
                     main_delay.Line[late_delay_tap++][j]*densityGain;
                 ++i;
             } while(--td);
@@ -1567,8 +1586,8 @@ void ReverbState::lateFaded(const size_t offset, const size_t todo, const float
                 const float fade1{densityStep*fadeCount};
                 const float gfade0{oldMidGain + oldMidStep*fadeCount};
                 const float gfade1{midStep*fadeCount};
-                mTempSamples[j][i] = lerp(out00, out01, frac)*gfade0 +
-                    lerp(out10, out11, frac)*gfade1 +
+                mTempSamples[j][i] = lerpf(out00, out01, frac)*gfade0 +
+                    lerpf(out10, out11, frac)*gfade1 +
                     main_delay.Line[late_delay_tap0++][j]*fade0 +
                     main_delay.Line[late_delay_tap1++][j]*fade1;
                 ++i;

+ 53 - 30
libs/openal-soft/Alc/effects/vmorpher.cpp

@@ -1,39 +1,62 @@
 /**
- * OpenAL cross platform audio library
+ * This file is part of the OpenAL Soft cross platform audio library
+ *
  * Copyright (C) 2019 by Anis A. Hireche
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
  *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Spherical-Harmonic-Transform nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
  *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "config.h"
 
-#include <cmath>
-#include <cstdlib>
 #include <algorithm>
+#include <array>
+#include <cstdlib>
 #include <functional>
-
-#include "alcmain.h"
-#include "alcontext.h"
-#include "alu.h"
-#include "effectslot.h"
-#include "math_defs.h"
+#include <iterator>
+
+#include "alc/effects/base.h"
+#include "almalloc.h"
+#include "alnumbers.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/context.h"
+#include "core/devformat.h"
+#include "core/device.h"
+#include "core/effectslot.h"
+#include "core/mixer.h"
+#include "intrusive_ptr.h"
 
 
 namespace {
 
+using uint = unsigned int;
+
 #define MAX_UPDATE_SAMPLES 256
 #define NUM_FORMANTS       4
 #define NUM_FILTERS        2
@@ -48,7 +71,7 @@ namespace {
 
 inline float Sin(uint index)
 {
-    constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
+    constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
     return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
 }
 
@@ -80,7 +103,7 @@ struct FormantFilter
 
     FormantFilter() = default;
     FormantFilter(float f0norm, float gain)
-      : mCoeff{std::tan(al::MathDefs<float>::Pi() * f0norm)}, mGain{gain}
+      : mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
     { }
 
     inline void process(const float *samplesIn, float *samplesOut, const size_t numInput)
@@ -138,8 +161,8 @@ struct VmorpherState final : public EffectState {
     alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
     alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
 
-    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
-    void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
+    void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
+    void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
         const EffectTarget target) override;
     void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
         const al::span<FloatBufferLine> samplesOut) override;
@@ -202,7 +225,7 @@ std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome p
 }
 
 
-void VmorpherState::deviceUpdate(const ALCdevice*, const Buffer&)
+void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&)
 {
     for(auto &e : mChans)
     {
@@ -214,10 +237,10 @@ void VmorpherState::deviceUpdate(const ALCdevice*, const Buffer&)
     }
 }
 
-void VmorpherState::update(const ALCcontext *context, const EffectSlot *slot,
+void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
     const EffectProps *props, const EffectTarget target)
 {
-    const ALCdevice *device{context->mDevice.get()};
+    const DeviceBase *device{context->mDevice};
     const float frequency{static_cast<float>(device->Frequency)};
     const float step{props->Vmorpher.Rate / frequency};
     mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
@@ -287,7 +310,7 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
 
             alignas(16) float blended[MAX_UPDATE_SAMPLES];
             for(size_t i{0u};i < td;i++)
-                blended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
+                blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
 
             /* Now, mix the processed sound data to the output. */
             MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,

+ 17 - 17
libs/openal-soft/Alc/inprogext.h

@@ -28,23 +28,6 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A
 #endif
 #endif
 
-#ifndef AL_SOFT_callback_buffer
-#define AL_SOFT_callback_buffer
-#define AL_BUFFER_CALLBACK_FUNCTION_SOFT         0x19A0
-#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT       0x19A1
-typedef ALsizei (AL_APIENTRY*LPALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numsamples);
-typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags);
-typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value);
-typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3);
-typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values);
-#ifdef AL_ALEXT_PROTOTYPES
-AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags);
-AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr);
-AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2);
-AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr);
-#endif
-#endif
-
 #ifndef AL_SOFT_bformat_hoa
 #define AL_SOFT_bformat_hoa
 #define AL_UNPACK_AMBISONIC_ORDER_SOFT           0x199D
@@ -66,6 +49,23 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *
 #endif
 #endif
 
+#ifndef AL_SOFT_hold_on_disconnect
+#define AL_SOFT_hold_on_disconnect
+#define AL_STOP_SOURCES_ON_DISCONNECT_SOFT       0x19AB
+#endif
+
+
+/* Non-standard export. Not part of any extension. */
+AL_API const ALchar* AL_APIENTRY alsoft_get_version(void);
+
+
+/* Functions from abandoned extenions. Only here for binary compatibility. */
+AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb,
+    const ALuint *buffers);
+
+AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname);
+AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif

文件差异内容过多而无法显示
+ 425 - 394
libs/openal-soft/Alc/panning.cpp


+ 0 - 784
libs/openal-soft/Alc/voice.cpp

@@ -1,784 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 1999-2007 by authors.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include "voice.h"
-
-#include <algorithm>
-#include <array>
-#include <atomic>
-#include <cassert>
-#include <climits>
-#include <cstddef>
-#include <cstdint>
-#include <iterator>
-#include <memory>
-#include <new>
-#include <utility>
-
-#include "alcmain.h"
-#include "albyte.h"
-#include "alconfig.h"
-#include "alcontext.h"
-#include "alnumeric.h"
-#include "aloptional.h"
-#include "alspan.h"
-#include "alstring.h"
-#include "alu.h"
-#include "async_event.h"
-#include "buffer_storage.h"
-#include "core/cpu_caps.h"
-#include "core/devformat.h"
-#include "core/filters/biquad.h"
-#include "core/filters/nfc.h"
-#include "core/filters/splitter.h"
-#include "core/fmt_traits.h"
-#include "core/logging.h"
-#include "core/mixer/defs.h"
-#include "core/mixer/hrtfdefs.h"
-#include "hrtf.h"
-#include "inprogext.h"
-#include "opthelpers.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "vector.h"
-#include "voice_change.h"
-
-struct CTag;
-#ifdef HAVE_SSE
-struct SSETag;
-#endif
-#ifdef HAVE_NEON
-struct NEONTag;
-#endif
-struct CopyTag;
-
-
-Resampler ResamplerDefault{Resampler::Linear};
-
-MixerFunc MixSamples{Mix_<CTag>};
-
-namespace {
-
-using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize,
-    const MixHrtfFilter *hrtfparams, const size_t BufferSize);
-using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples,
-    const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams,
-    const size_t BufferSize);
-
-HrtfMixerFunc MixHrtfSamples{MixHrtf_<CTag>};
-HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_<CTag>};
-
-inline MixerFunc SelectMixer()
-{
-#ifdef HAVE_NEON
-    if((CPUCapFlags&CPU_CAP_NEON))
-        return Mix_<NEONTag>;
-#endif
-#ifdef HAVE_SSE
-    if((CPUCapFlags&CPU_CAP_SSE))
-        return Mix_<SSETag>;
-#endif
-    return Mix_<CTag>;
-}
-
-inline HrtfMixerFunc SelectHrtfMixer()
-{
-#ifdef HAVE_NEON
-    if((CPUCapFlags&CPU_CAP_NEON))
-        return MixHrtf_<NEONTag>;
-#endif
-#ifdef HAVE_SSE
-    if((CPUCapFlags&CPU_CAP_SSE))
-        return MixHrtf_<SSETag>;
-#endif
-    return MixHrtf_<CTag>;
-}
-
-inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
-{
-#ifdef HAVE_NEON
-    if((CPUCapFlags&CPU_CAP_NEON))
-        return MixHrtfBlend_<NEONTag>;
-#endif
-#ifdef HAVE_SSE
-    if((CPUCapFlags&CPU_CAP_SSE))
-        return MixHrtfBlend_<SSETag>;
-#endif
-    return MixHrtfBlend_<CTag>;
-}
-
-} // namespace
-
-
-void aluInitMixer()
-{
-    if(auto resopt = ConfigValueStr(nullptr, nullptr, "resampler"))
-    {
-        struct ResamplerEntry {
-            const char name[16];
-            const Resampler resampler;
-        };
-        constexpr ResamplerEntry ResamplerList[]{
-            { "none", Resampler::Point },
-            { "point", Resampler::Point },
-            { "linear", Resampler::Linear },
-            { "cubic", Resampler::Cubic },
-            { "bsinc12", Resampler::BSinc12 },
-            { "fast_bsinc12", Resampler::FastBSinc12 },
-            { "bsinc24", Resampler::BSinc24 },
-            { "fast_bsinc24", Resampler::FastBSinc24 },
-        };
-
-        const char *str{resopt->c_str()};
-        if(al::strcasecmp(str, "bsinc") == 0)
-        {
-            WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
-            str = "bsinc12";
-        }
-        else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0)
-        {
-            WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
-            str = "cubic";
-        }
-
-        auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList),
-            [str](const ResamplerEntry &entry) -> bool
-            { return al::strcasecmp(str, entry.name) == 0; });
-        if(iter == std::end(ResamplerList))
-            ERR("Invalid resampler: %s\n", str);
-        else
-            ResamplerDefault = iter->resampler;
-    }
-
-    MixSamples = SelectMixer();
-    MixHrtfBlendSamples = SelectHrtfBlendMixer();
-    MixHrtfSamples = SelectHrtfMixer();
-}
-
-
-namespace {
-
-void SendSourceStoppedEvent(ALCcontext *context, uint id)
-{
-    RingBuffer *ring{context->mAsyncEvents.get()};
-    auto evt_vec = ring->getWriteVector();
-    if(evt_vec.first.len < 1) return;
-
-    AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
-    evt->u.srcstate.id = id;
-    evt->u.srcstate.state = VChangeState::Stop;
-
-    ring->writeAdvance(1);
-}
-
-
-const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst,
-    const al::span<const float> src, int type)
-{
-    switch(type)
-    {
-    case AF_None:
-        lpfilter.clear();
-        hpfilter.clear();
-        break;
-
-    case AF_LowPass:
-        lpfilter.process(src, dst);
-        hpfilter.clear();
-        return dst;
-    case AF_HighPass:
-        lpfilter.clear();
-        hpfilter.process(src, dst);
-        return dst;
-
-    case AF_BandPass:
-        DualBiquad{lpfilter, hpfilter}.process(src, dst);
-        return dst;
-    }
-    return src.data();
-}
-
-
-void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
-    const size_t samples) noexcept
-{
-#define HANDLE_FMT(T)  case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
-    switch(srctype)
-    {
-    HANDLE_FMT(FmtUByte);
-    HANDLE_FMT(FmtShort);
-    HANDLE_FMT(FmtFloat);
-    HANDLE_FMT(FmtDouble);
-    HANDLE_FMT(FmtMulaw);
-    HANDLE_FMT(FmtAlaw);
-    }
-#undef HANDLE_FMT
-}
-
-float *LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *&bufferLoopItem,
-    const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan,
-    size_t dataPosInt, al::span<float> srcBuffer)
-{
-    const uint LoopStart{buffer->mLoopStart};
-    const uint LoopEnd{buffer->mLoopEnd};
-    ASSUME(LoopEnd > LoopStart);
-
-    /* If current pos is beyond the loop range, do not loop */
-    if(!bufferLoopItem || dataPosInt >= LoopEnd)
-    {
-        bufferLoopItem = nullptr;
-
-        /* Load what's left to play from the buffer */
-        const size_t DataRem{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)};
-
-        const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
-        LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
-        srcBuffer = srcBuffer.subspan(DataRem);
-    }
-    else
-    {
-        /* Load what's left of this loop iteration */
-        const size_t DataRem{minz(srcBuffer.size(), LoopEnd-dataPosInt)};
-
-        const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
-        LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
-        srcBuffer = srcBuffer.subspan(DataRem);
-
-        /* Load any repeats of the loop we can to fill the buffer. */
-        const auto LoopSize = static_cast<size_t>(LoopEnd - LoopStart);
-        while(!srcBuffer.empty())
-        {
-            const size_t DataSize{minz(srcBuffer.size(), LoopSize)};
-
-            Data = buffer->mSamples + (LoopStart*numChannels + chan)*sampleSize;
-
-            LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize);
-            srcBuffer = srcBuffer.subspan(DataSize);
-        }
-    }
-    return srcBuffer.begin();
-}
-
-float *LoadBufferCallback(VoiceBufferItem *buffer, const size_t numChannels,
-    const FmtType sampleType, const size_t sampleSize, const size_t chan,
-    size_t numCallbackSamples, al::span<float> srcBuffer)
-{
-    /* Load what's left to play from the buffer */
-    const size_t DataRem{minz(srcBuffer.size(), numCallbackSamples)};
-
-    const al::byte *Data{buffer->mSamples + chan*sampleSize};
-    LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataRem);
-    srcBuffer = srcBuffer.subspan(DataRem);
-
-    return srcBuffer.begin();
-}
-
-float *LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
-    const size_t numChannels, const FmtType sampleType, const size_t sampleSize, const size_t chan,
-    size_t dataPosInt, al::span<float> srcBuffer)
-{
-    /* Crawl the buffer queue to fill in the temp buffer */
-    while(buffer && !srcBuffer.empty())
-    {
-        if(dataPosInt >= buffer->mSampleLen)
-        {
-            dataPosInt -= buffer->mSampleLen;
-            buffer = buffer->mNext.load(std::memory_order_acquire);
-            if(!buffer) buffer = bufferLoopItem;
-            continue;
-        }
-
-        const size_t DataSize{minz(srcBuffer.size(), buffer->mSampleLen-dataPosInt)};
-
-        const al::byte *Data{buffer->mSamples + (dataPosInt*numChannels + chan)*sampleSize};
-        LoadSamples(srcBuffer.data(), Data, numChannels, sampleType, DataSize);
-        srcBuffer = srcBuffer.subspan(DataSize);
-        if(srcBuffer.empty()) break;
-
-        dataPosInt = 0;
-        buffer = buffer->mNext.load(std::memory_order_acquire);
-        if(!buffer) buffer = bufferLoopItem;
-    }
-
-    return srcBuffer.begin();
-}
-
-
-void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms,
-    const float TargetGain, const uint Counter, uint OutPos, const uint IrSize,
-    ALCdevice *Device)
-{
-    auto &HrtfSamples = Device->HrtfSourceData;
-    /* Source HRTF mixing needs to include the direct delay so it remains
-     * aligned with the direct mix's HRTF filtering.
-     */
-    float2 *AccumSamples{Device->HrtfAccumData + HrtfDirectDelay};
-
-    /* Copy the HRTF history and new input samples into a temp buffer. */
-    auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(),
-        std::begin(HrtfSamples));
-    std::copy_n(samples, DstBufferSize, src_iter);
-    /* Copy the last used samples back into the history buffer for later. */
-    std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(),
-        parms.Hrtf.History.begin());
-
-    /* If fading and this is the first mixing pass, fade between the IRs. */
-    uint fademix{0u};
-    if(Counter && OutPos == 0)
-    {
-        fademix = minu(DstBufferSize, Counter);
-
-        float gain{TargetGain};
-
-        /* The new coefficients need to fade in completely since they're
-         * replacing the old ones. To keep the gain fading consistent,
-         * interpolate between the old and new target gains given how much of
-         * the fade time this mix handles.
-         */
-        if(Counter > fademix)
-        {
-            const float a{static_cast<float>(fademix) / static_cast<float>(Counter)};
-            gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
-        }
-        MixHrtfFilter hrtfparams;
-        hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
-        hrtfparams.Delay = parms.Hrtf.Target.Delay;
-        hrtfparams.Gain = 0.0f;
-        hrtfparams.GainStep = gain / static_cast<float>(fademix);
-
-        MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams,
-            fademix);
-        /* Update the old parameters with the result. */
-        parms.Hrtf.Old = parms.Hrtf.Target;
-        parms.Hrtf.Old.Gain = gain;
-        OutPos += fademix;
-    }
-
-    if(fademix < DstBufferSize)
-    {
-        const uint todo{DstBufferSize - fademix};
-        float gain{TargetGain};
-
-        /* Interpolate the target gain if the gain fading lasts longer than
-         * this mix.
-         */
-        if(Counter > DstBufferSize)
-        {
-            const float a{static_cast<float>(todo) / static_cast<float>(Counter-fademix)};
-            gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
-        }
-
-        MixHrtfFilter hrtfparams;
-        hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
-        hrtfparams.Delay = parms.Hrtf.Target.Delay;
-        hrtfparams.Gain = parms.Hrtf.Old.Gain;
-        hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo);
-        MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo);
-        /* Store the now-current gain for next time. */
-        parms.Hrtf.Old.Gain = gain;
-    }
-}
-
-void DoNfcMix(const al::span<const float> samples, FloatBufferLine *OutBuffer, DirectParams &parms,
-    const float *TargetGains, const uint Counter, const uint OutPos, ALCdevice *Device)
-{
-    using FilterProc = void (NfcFilter::*)(const al::span<const float>, float*);
-    static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{
-        nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3};
-
-    float *CurrentGains{parms.Gains.Current.data()};
-    MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos);
-    ++OutBuffer;
-    ++CurrentGains;
-    ++TargetGains;
-
-    const al::span<float> nfcsamples{Device->NfcSampleData, samples.size()};
-    size_t order{1};
-    while(const size_t chancount{Device->NumChannelsPerOrder[order]})
-    {
-        (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data());
-        MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos);
-        OutBuffer += chancount;
-        CurrentGains += chancount;
-        TargetGains += chancount;
-        if(++order == MaxAmbiOrder+1)
-            break;
-    }
-}
-
-} // namespace
-
-void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
-{
-    static constexpr std::array<float,MAX_OUTPUT_CHANNELS> SilentTarget{};
-
-    ASSUME(SamplesToDo > 0);
-
-    /* Get voice info */
-    uint DataPosInt{mPosition.load(std::memory_order_relaxed)};
-    uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)};
-    VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)};
-    VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)};
-    const FmtType SampleType{mFmtType};
-    const uint SampleSize{mSampleSize};
-    const uint increment{mStep};
-    if UNLIKELY(increment < 1)
-    {
-        /* If the voice is supposed to be stopping but can't be mixed, just
-         * stop it before bailing.
-         */
-        if(vstate == Stopping)
-            mPlayState.store(Stopped, std::memory_order_release);
-        return;
-    }
-
-    ASSUME(SampleSize > 0);
-
-    const size_t FrameSize{mChans.size() * SampleSize};
-    ASSUME(FrameSize > 0);
-
-    ALCdevice *Device{Context->mDevice.get()};
-    const uint NumSends{Device->NumAuxSends};
-    const uint IrSize{Device->mIrSize};
-
-    ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ?
-                           Resample_<CopyTag,CTag> : mResampler};
-
-    uint Counter{(mFlags&VoiceIsFading) ? SamplesToDo : 0};
-    if(!Counter)
-    {
-        /* No fading, just overwrite the old/current params. */
-        for(auto &chandata : mChans)
-        {
-            {
-                DirectParams &parms = chandata.mDryParams;
-                if(!(mFlags&VoiceHasHrtf))
-                    parms.Gains.Current = parms.Gains.Target;
-                else
-                    parms.Hrtf.Old = parms.Hrtf.Target;
-            }
-            for(uint send{0};send < NumSends;++send)
-            {
-                if(mSend[send].Buffer.empty())
-                    continue;
-
-                SendParams &parms = chandata.mWetParams[send];
-                parms.Gains.Current = parms.Gains.Target;
-            }
-        }
-    }
-    else if UNLIKELY(!BufferListItem)
-        Counter = std::min(Counter, 64u);
-
-    uint buffers_done{0u};
-    uint OutPos{0u};
-    do {
-        /* Figure out how many buffer samples will be needed */
-        uint DstBufferSize{SamplesToDo - OutPos};
-        uint SrcBufferSize;
-
-        if(increment <= MixerFracOne)
-        {
-            /* Calculate the last written dst sample pos. */
-            uint64_t DataSize64{DstBufferSize - 1};
-            /* Calculate the last read src sample pos. */
-            DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
-            /* +1 to get the src sample count, include padding. */
-            DataSize64 += 1 + MaxResamplerPadding;
-
-            /* Result is guaranteed to be <= BufferLineSize+MaxResamplerPadding
-             * since we won't use more src samples than dst samples+padding.
-             */
-            SrcBufferSize = static_cast<uint>(DataSize64);
-        }
-        else
-        {
-            uint64_t DataSize64{DstBufferSize};
-            /* Calculate the end src sample pos, include padding. */
-            DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
-            DataSize64 += MaxResamplerPadding;
-
-            if(DataSize64 <= BufferLineSize + MaxResamplerPadding)
-                SrcBufferSize = static_cast<uint>(DataSize64);
-            else
-            {
-                /* If the source size got saturated, we can't fill the desired
-                 * dst size. Figure out how many samples we can actually mix.
-                 */
-                SrcBufferSize = BufferLineSize + MaxResamplerPadding;
-
-                DataSize64 = SrcBufferSize - MaxResamplerPadding;
-                DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
-                if(DataSize64 < DstBufferSize)
-                {
-                    /* Some mixers require being 16-byte aligned, so also limit
-                     * to a multiple of 4 samples to maintain alignment.
-                     */
-                    DstBufferSize = static_cast<uint>(DataSize64) & ~3u;
-                }
-            }
-        }
-
-        if((mFlags&(VoiceIsCallback|VoiceCallbackStopped)) == VoiceIsCallback && BufferListItem)
-        {
-            /* Exclude resampler pre-padding from the needed size. */
-            const uint toLoad{SrcBufferSize - (MaxResamplerPadding>>1)};
-            if(toLoad > mNumCallbackSamples)
-            {
-                const size_t byteOffset{mNumCallbackSamples*FrameSize};
-                const size_t needBytes{toLoad*FrameSize - byteOffset};
-
-                const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
-                    &BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
-                if(gotBytes < 1)
-                    mFlags |= VoiceCallbackStopped;
-                else if(static_cast<uint>(gotBytes) < needBytes)
-                {
-                    mFlags |= VoiceCallbackStopped;
-                    mNumCallbackSamples += static_cast<uint>(static_cast<uint>(gotBytes) /
-                        FrameSize);
-                }
-                else
-                    mNumCallbackSamples = toLoad;
-            }
-        }
-
-        const size_t num_chans{mChans.size()};
-        size_t chan_idx{0};
-        ASSUME(DstBufferSize > 0);
-        for(auto &chandata : mChans)
-        {
-            const al::span<float> SrcData{Device->SourceData, SrcBufferSize};
-
-            /* Load the previous samples into the source data first, then load
-             * what we can from the buffer queue.
-             */
-            auto srciter = std::copy_n(chandata.mPrevSamples.begin(), MaxResamplerPadding>>1,
-                SrcData.begin());
-
-            if UNLIKELY(!BufferListItem)
-            {
-                /* When loading from a voice that ended prematurely, only take
-                 * the samples that get closest to 0 amplitude. This helps
-                 * certain sounds fade out better.
-                 */
-                auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
-                { return std::abs(lhs) < std::abs(rhs); };
-                auto input = chandata.mPrevSamples.begin() + (MaxResamplerPadding>>1);
-                auto in_end = std::min_element(input, chandata.mPrevSamples.end(), abs_lt);
-                srciter = std::copy(input, in_end, srciter);
-            }
-            else if((mFlags&VoiceIsStatic))
-                srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, num_chans, SampleType,
-                    SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()});
-            else if((mFlags&VoiceIsCallback))
-                srciter = LoadBufferCallback(BufferListItem, num_chans, SampleType, SampleSize,
-                    chan_idx, mNumCallbackSamples, {srciter, SrcData.end()});
-            else
-                srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, num_chans, SampleType,
-                    SampleSize, chan_idx, DataPosInt, {srciter, SrcData.end()});
-
-            if UNLIKELY(srciter != SrcData.end())
-            {
-                /* If the source buffer wasn't filled, copy the last sample for
-                 * the remaining buffer. Ideally it should have ended with
-                 * silence, but if not the gain fading should help avoid clicks
-                 * from sudden amplitude changes.
-                 */
-                const float sample{*(srciter-1)};
-                std::fill(srciter, SrcData.end(), sample);
-            }
-
-            /* Store the last source samples used for next time. */
-            std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>MixerFracBits],
-                chandata.mPrevSamples.size(), chandata.mPrevSamples.begin());
-
-            /* Resample, then apply ambisonic upsampling as needed. */
-            float *ResampledData{Resample(&mResampleState, &SrcData[MaxResamplerPadding>>1],
-                DataPosFrac, increment, {Device->ResampledData, DstBufferSize})};
-            if((mFlags&VoiceIsAmbisonic))
-                chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize},
-                    chandata.mAmbiScale);
-
-            /* Now filter and mix to the appropriate outputs. */
-            float (&FilterBuf)[BufferLineSize] = Device->FilteredData;
-            {
-                DirectParams &parms = chandata.mDryParams;
-                const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf,
-                    {ResampledData, DstBufferSize}, mDirect.FilterType)};
-
-                if((mFlags&VoiceHasHrtf))
-                {
-                    const float TargetGain{UNLIKELY(vstate == Stopping) ? 0.0f :
-                        parms.Hrtf.Target.Gain};
-                    DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, IrSize,
-                        Device);
-                }
-                else if((mFlags&VoiceHasNfc))
-                {
-                    const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
-                        : parms.Gains.Target.data()};
-                    DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, TargetGains,
-                        Counter, OutPos, Device);
-                }
-                else
-                {
-                    const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
-                        : parms.Gains.Target.data()};
-                    MixSamples({samples, DstBufferSize}, mDirect.Buffer,
-                        parms.Gains.Current.data(), TargetGains, Counter, OutPos);
-                }
-            }
-
-            for(uint send{0};send < NumSends;++send)
-            {
-                if(mSend[send].Buffer.empty())
-                    continue;
-
-                SendParams &parms = chandata.mWetParams[send];
-                const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf,
-                    {ResampledData, DstBufferSize}, mSend[send].FilterType)};
-
-                const float *TargetGains{UNLIKELY(vstate == Stopping) ? SilentTarget.data()
-                    : parms.Gains.Target.data()};
-                MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
-                    parms.Gains.Current.data(), TargetGains, Counter, OutPos);
-            }
-
-            ++chan_idx;
-        }
-        /* Update positions */
-        DataPosFrac += increment*DstBufferSize;
-        const uint SrcSamplesDone{DataPosFrac>>MixerFracBits};
-        DataPosInt  += SrcSamplesDone;
-        DataPosFrac &= MixerFracMask;
-
-        OutPos += DstBufferSize;
-        Counter = maxu(DstBufferSize, Counter) - DstBufferSize;
-
-        if UNLIKELY(!BufferListItem)
-        {
-            /* Do nothing extra when there's no buffers. */
-        }
-        else if((mFlags&VoiceIsStatic))
-        {
-            if(BufferLoopItem)
-            {
-                /* Handle looping static source */
-                const uint LoopStart{BufferListItem->mLoopStart};
-                const uint LoopEnd{BufferListItem->mLoopEnd};
-                if(DataPosInt >= LoopEnd)
-                {
-                    assert(LoopEnd > LoopStart);
-                    DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
-                }
-            }
-            else
-            {
-                /* Handle non-looping static source */
-                if(DataPosInt >= BufferListItem->mSampleLen)
-                {
-                    BufferListItem = nullptr;
-                    break;
-                }
-            }
-        }
-        else if((mFlags&VoiceIsCallback))
-        {
-            if(SrcSamplesDone < mNumCallbackSamples)
-            {
-                const size_t byteOffset{SrcSamplesDone*FrameSize};
-                const size_t byteEnd{mNumCallbackSamples*FrameSize};
-                al::byte *data{BufferListItem->mSamples};
-                std::copy(data+byteOffset, data+byteEnd, data);
-                mNumCallbackSamples -= SrcSamplesDone;
-            }
-            else
-            {
-                BufferListItem = nullptr;
-                mNumCallbackSamples = 0;
-            }
-        }
-        else
-        {
-            /* Handle streaming source */
-            do {
-                if(BufferListItem->mSampleLen > DataPosInt)
-                    break;
-
-                DataPosInt -= BufferListItem->mSampleLen;
-
-                ++buffers_done;
-                BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed);
-                if(!BufferListItem) BufferListItem = BufferLoopItem;
-            } while(BufferListItem);
-        }
-    } while(OutPos < SamplesToDo);
-
-    mFlags |= VoiceIsFading;
-
-    /* Don't update positions and buffers if we were stopping. */
-    if UNLIKELY(vstate == Stopping)
-    {
-        mPlayState.store(Stopped, std::memory_order_release);
-        return;
-    }
-
-    /* Capture the source ID in case it's reset for stopping. */
-    const uint SourceID{mSourceID.load(std::memory_order_relaxed)};
-
-    /* Update voice info */
-    mPosition.store(DataPosInt, std::memory_order_relaxed);
-    mPositionFrac.store(DataPosFrac, std::memory_order_relaxed);
-    mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed);
-    if(!BufferListItem)
-    {
-        mLoopBuffer.store(nullptr, std::memory_order_relaxed);
-        mSourceID.store(0u, std::memory_order_relaxed);
-    }
-    std::atomic_thread_fence(std::memory_order_release);
-
-    /* Send any events now, after the position/buffer info was updated. */
-    const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)};
-    if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
-    {
-        RingBuffer *ring{Context->mAsyncEvents.get()};
-        auto evt_vec = ring->getWriteVector();
-        if(evt_vec.first.len > 0)
-        {
-            AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}};
-            evt->u.bufcomp.id = SourceID;
-            evt->u.bufcomp.count = buffers_done;
-            ring->writeAdvance(1);
-        }
-    }
-
-    if(!BufferListItem)
-    {
-        /* If the voice just ended, set it to Stopping so the next render
-         * ensures any residual noise fades to 0 amplitude.
-         */
-        mPlayState.store(Stopping, std::memory_order_release);
-        if((enabledevt&EventType_SourceStateChange))
-            SendSourceStoppedEvent(Context, SourceID);
-    }
-}

+ 1 - 0
libs/openal-soft/BSD-3Clause

@@ -1,6 +1,7 @@
 Portions of this software are licensed under the BSD 3-Clause license.
 
 Copyright (c) 2015, Archontis Politis
+Copyright (c) 2019, Anis A. Hireche
 Copyright (c) 2019, Christopher Robinson
 All rights reserved.
 

+ 250 - 146
libs/openal-soft/CMakeLists.txt

@@ -2,21 +2,24 @@
 
 cmake_minimum_required(VERSION 3.0.2)
 
-# The workaround for try_compile failing with code signing
-# since cmake-3.18.2, not required
-set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
-    "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
-    "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO)
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
-
-# Fix compile failure with armv7 deployment target >= 11.0, xcode clang will
-# report:
-# error: invalid iOS deployment version '--target=armv7-apple-ios13.6',
-# iOS 10 is the maximum deployment target for 32-bit targets
-# If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest
-# deployment target
+if(APPLE)
+    # The workaround for try_compile failing with code signing
+    # since cmake-3.18.2, not required
+    set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
+        ${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}
+        "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
+        "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED")
+    set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO)
+    set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
+endif()
+
 if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
+    # Fix compile failure with armv7 deployment target >= 11.0, xcode clang
+    # will report:
+    # error: invalid iOS deployment version '--target=armv7-apple-ios13.6',
+    # iOS 10 is the maximum deployment target for 32-bit targets
+    # If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest
+    # deployment target
     if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*")
         if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
             OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0")
@@ -69,26 +72,31 @@ include(CheckCXXCompilerFlag)
 include(CheckCSourceCompiles)
 include(CheckCXXSourceCompiles)
 include(CheckStructHasMember)
+include(CMakePackageConfigHelpers)
 include(GNUInstallDirs)
 
+find_package(PkgConfig)
+
 
 option(ALSOFT_DLOPEN  "Check for the dlopen API for loading optional libs"  ON)
 
 option(ALSOFT_WERROR  "Treat compile warnings as errors"      OFF)
 
-option(ALSOFT_UTILS "Build utility programs"  OFF)
-option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" ON)
+option(ALSOFT_UTILS "Build utility programs"  ON)
+option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF)
 
-option(ALSOFT_EXAMPLES  "Build example programs"  OFF)
+option(ALSOFT_EXAMPLES  "Build example programs"  ON)
 
-option(ALSOFT_INSTALL "Install main library" OFF)
-option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" OFF)
-option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" OFF)
-option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" OFF)
-option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
-option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" OFF)
+option(ALSOFT_INSTALL "Install main library" ON)
+option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON)
+option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" ON)
+option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" ON)
+option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON)
+option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON)
 option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
 
+option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
+
 if(DEFINED SHARE_INSTALL_DIR)
     message(WARNING "SHARE_INSTALL_DIR is deprecated.  Use the variables provided by the GNUInstallDirs module instead")
     set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}")
@@ -115,7 +123,7 @@ set(LINKER_FLAGS )
 set(EXTRA_LIBS )
 
 if(WIN32)
-    set(CPP_DEFS ${CPP_DEFS} _WIN32)
+    set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX)
     if(MINGW)
         set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO)
     endif()
@@ -142,8 +150,8 @@ if(NOT LIBTYPE)
 endif()
 
 set(LIB_MAJOR_VERSION "1")
-set(LIB_MINOR_VERSION "21")
-set(LIB_REVISION "1")
+set(LIB_MINOR_VERSION "22")
+set(LIB_REVISION "0")
 set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
 set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
 
@@ -196,20 +204,22 @@ else()
 endif()
 unset(OLD_REQUIRED_LIBRARIES)
 
-# Include liblog for Android logging
-check_library_exists(log __android_log_print "" HAVE_LIBLOG)
-if(HAVE_LIBLOG)
-    set(EXTRA_LIBS log ${EXTRA_LIBS})
-    set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
+if(ANDROID)
+    # Include liblog for Android logging
+    check_library_exists(log __android_log_print "" HAVE_LIBLOG)
+    if(HAVE_LIBLOG)
+        set(EXTRA_LIBS log ${EXTRA_LIBS})
+        set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
+    endif()
 endif()
 
 if(MSVC)
-    set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS NOMINMAX)
+    set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS)
     check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH)
     if(HAVE_PERMISSIVE_SWITCH)
         set(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->)
     endif()
-    set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4268 /wd4324 /wd5030)
+    set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030)
     # Remove /W3, which is added by default, since we set /W4. Some build
     # generators with MSVC complain about both /W3 and /W4 being specified.
     foreach(flag_var  CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
@@ -345,17 +355,17 @@ endif()
 
 set(SSE2_SWITCH "")
 
-set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
 if(NOT MSVC)
+    set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
     # Yes GCC, really don't accept command line options you don't support
     set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror")
+    check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH)
+    if(HAVE_MSSE2_SWITCH)
+        set(SSE2_SWITCH "-msse2")
+    endif()
+    set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
+    unset(OLD_REQUIRED_FLAGS)
 endif()
-check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH)
-if(HAVE_MSSE2_SWITCH)
-    set(SSE2_SWITCH "-msse2")
-endif()
-set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
-unset(OLD_REQUIRED_FLAGS)
 
 check_include_file(xmmintrin.h HAVE_XMMINTRIN_H)
 check_include_file(emmintrin.h HAVE_EMMINTRIN_H)
@@ -369,20 +379,25 @@ set(HAVE_SSE3       0)
 set(HAVE_SSE4_1     0)
 set(HAVE_NEON       0)
 
-# Check for SSE+SSE2 support
+# Check for SSE support
 option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF)
-option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF)
-if(HAVE_XMMINTRIN_H AND HAVE_EMMINTRIN_H)
+if(HAVE_XMMINTRIN_H)
     option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON)
-    option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
-    if(ALSOFT_CPUEXT_SSE AND ALSOFT_CPUEXT_SSE2)
+    if(ALSOFT_CPUEXT_SSE)
         set(HAVE_SSE 1)
-        set(HAVE_SSE2 1)
     endif()
 endif()
 if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE)
     message(FATAL_ERROR "Failed to enabled required SSE CPU extensions")
 endif()
+
+option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF)
+if(HAVE_EMMINTRIN_H)
+    option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
+    if(HAVE_SSE AND ALSOFT_CPUEXT_SSE2)
+        set(HAVE_SSE2 1)
+    endif()
+endif()
 if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2)
     message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions")
 endif()
@@ -435,7 +450,14 @@ set(FPMATH_SET "0")
 if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
     option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE)
     if(ALSOFT_ENABLE_SSE2_CODEGEN)
-        if(SSE2_SWITCH OR NOT MSVC)
+        if(MSVC)
+            check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2)
+            if(HAVE_ARCH_SSE2)
+                set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2")
+                set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
+                set(FPMATH_SET 2)
+            endif()
+        elseif(SSE2_SWITCH)
             check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2)
             if(HAVE_MFPMATH_SSE_2)
                 set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse)
@@ -447,13 +469,6 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2)
             # OSs don't guarantee this on 32-bit, so externally-callable
             # functions need to ensure an aligned stack.
             set(EXPORT_DECL "${EXPORT_DECL} __attribute__((force_align_arg_pointer))")
-        elseif(MSVC)
-            check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2)
-            if(HAVE_ARCH_SSE2)
-                set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2")
-                set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
-                set(FPMATH_SET 2)
-            endif()
         endif()
     endif()
 endif()
@@ -551,51 +566,11 @@ if(NOT WIN32)
         check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP)
         if(NOT HAVE_PTHREAD_SETNAME_NP)
             check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP)
-        else()
-            check_c_source_compiles("
-#include <pthread.h>
-#include <pthread_np.h>
-int main()
-{
-    pthread_setname_np(\"testname\");
-    return 0;
-}"
-                PTHREAD_SETNAME_NP_ONE_PARAM
-            )
-            check_c_source_compiles("
-#include <pthread.h>
-#include <pthread_np.h>
-int main()
-{
-    pthread_setname_np(pthread_self(), \"%s\", \"testname\");
-    return 0;
-}"
-                PTHREAD_SETNAME_NP_THREE_PARAMS
-            )
         endif()
     else()
         check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP)
         if(NOT HAVE_PTHREAD_SETNAME_NP)
             check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP)
-        else()
-            check_c_source_compiles("
-#include <pthread.h>
-int main()
-{
-    pthread_setname_np(\"testname\");
-    return 0;
-}"
-                PTHREAD_SETNAME_NP_ONE_PARAM
-            )
-            check_c_source_compiles("
-#include <pthread.h>
-int main()
-{
-    pthread_setname_np(pthread_self(), \"%s\", \"testname\");
-    return 0;
-}"
-                PTHREAD_SETNAME_NP_THREE_PARAMS
-            )
         endif()
     endif()
 endif()
@@ -615,17 +590,19 @@ set(COMMON_OBJS
     common/alfstream.h
     common/almalloc.cpp
     common/almalloc.h
+    common/alnumbers.h
     common/alnumeric.h
     common/aloptional.h
     common/alspan.h
     common/alstring.cpp
     common/alstring.h
     common/atomic.h
+    common/comptr.h
     common/dynload.cpp
     common/dynload.h
     common/intrusive_ptr.h
-    common/math_defs.h
     common/opthelpers.h
+    common/phase_shifter.h
     common/polyphase_resampler.cpp
     common/polyphase_resampler.h
     common/pragmadefs.h
@@ -642,17 +619,32 @@ set(COMMON_OBJS
 set(CORE_OBJS
     core/ambdec.cpp
     core/ambdec.h
+    core/ambidefs.cpp
     core/ambidefs.h
+    core/async_event.h
+    core/bformatdec.cpp
+    core/bformatdec.h
     core/bs2b.cpp
     core/bs2b.h
     core/bsinc_defs.h
     core/bsinc_tables.cpp
     core/bsinc_tables.h
     core/bufferline.h
+    core/buffer_storage.cpp
+    core/buffer_storage.h
+    core/context.cpp
+    core/context.h
+    core/converter.cpp
+    core/converter.h
     core/cpu_caps.cpp
     core/cpu_caps.h
     core/devformat.cpp
     core/devformat.h
+    core/device.cpp
+    core/device.h
+    core/effects/base.h
+    core/effectslot.cpp
+    core/effectslot.h
     core/except.cpp
     core/except.h
     core/filters/biquad.h
@@ -665,12 +657,57 @@ set(CORE_OBJS
     core/fmt_traits.h
     core/fpu_ctrl.cpp
     core/fpu_ctrl.h
+    core/front_stablizer.h
+    core/helpers.cpp
+    core/helpers.h
+    core/hrtf.cpp
+    core/hrtf.h
     core/logging.cpp
     core/logging.h
     core/mastering.cpp
     core/mastering.h
+    core/mixer.cpp
+    core/mixer.h
+    core/resampler_limits.h
     core/uhjfilter.cpp
     core/uhjfilter.h
+    core/uiddefs.cpp
+    core/voice.cpp
+    core/voice.h
+    core/voice_change.h)
+
+set(HAVE_RTKIT 0)
+option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE)
+find_package(DBus1 QUIET)
+if(DBus1_FOUND)
+    option(ALSOFT_RTKIT "Enable RTKit support" ON)
+    if(ALSOFT_RTKIT)
+        set(HAVE_RTKIT 1)
+        set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h)
+        if(WIN32 OR HAVE_DLFCN_H)
+            set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS})
+            set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS})
+        else()
+            set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES})
+        endif()
+    endif()
+else()
+    set(MISSING_VARS "")
+    if(NOT DBus1_INCLUDE_DIRS)
+        set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS")
+    endif()
+    if(NOT DBus1_LIBRARIES)
+        set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES")
+    endif()
+    message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})")
+    unset(MISSING_VARS)
+endif()
+if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT)
+    message(FATAL_ERROR "Failed to enabled required RTKit support")
+endif()
+
+# Default mixers, always available
+set(CORE_OBJS ${CORE_OBJS}
     core/mixer/defs.h
     core/mixer/hrtfbase.h
     core/mixer/hrtfdefs.h
@@ -691,6 +728,7 @@ set(OPENAL_OBJS
     al/effects/dedicated.cpp
     al/effects/distortion.cpp
     al/effects/echo.cpp
+	al/effects/effects.cpp
     al/effects/effects.h
     al/effects/equalizer.cpp
     al/effects/fshifter.cpp
@@ -714,22 +752,14 @@ set(OPENAL_OBJS
 # ALC and related routines
 set(ALC_OBJS
     alc/alc.cpp
-    alc/alcmain.h
     alc/alu.cpp
     alc/alu.h
     alc/alconfig.cpp
     alc/alconfig.h
-    alc/alcontext.h
-    alc/async_event.h
-    alc/bformatdec.cpp
-    alc/bformatdec.h
-    alc/buffer_storage.cpp
-    alc/buffer_storage.h
-    alc/compat.h
-    alc/converter.cpp
-    alc/converter.h
-    alc/effectslot.cpp
-    alc/effectslot.h
+    alc/context.cpp
+    alc/context.h
+    alc/device.cpp
+    alc/device.h
     alc/effects/base.h
     alc/effects/autowah.cpp
     alc/effects/chorus.cpp
@@ -745,22 +775,42 @@ set(ALC_OBJS
     alc/effects/pshifter.cpp
     alc/effects/reverb.cpp
     alc/effects/vmorpher.cpp
-    alc/front_stablizer.h
-    alc/helpers.cpp
-    alc/hrtf.cpp
-    alc/hrtf.h
     alc/inprogext.h
-    alc/panning.cpp
-    alc/uiddefs.cpp
-    alc/voice.cpp
-    alc/voice.h
-    alc/voice_change.h)
+    alc/panning.cpp)
+
+if (ALSOFT_EAX)
+    set(OPENAL_OBJS
+        ${OPENAL_OBJS}
+        al/eax_api.cpp
+        al/eax_api.h
+        al/eax_eax_call.cpp
+        al/eax_eax_call.h
+        al/eax_effect.cpp
+        al/eax_effect.h
+        al/eax_exception.cpp
+        al/eax_exception.h
+        al/eax_fx_slot_index.cpp
+        al/eax_fx_slot_index.h
+        al/eax_fx_slots.cpp
+        al/eax_fx_slots.h
+        al/eax_globals.cpp
+        al/eax_globals.h
+        al/eax_utils.cpp
+        al/eax_utils.h
+        al/eax_x_ram.cpp
+        al/eax_x_ram.h
+)
+endif ()
 
 # Include SIMD mixers
 set(CPU_EXTS "Default")
+if(HAVE_SSE)
+    set(CORE_OBJS  ${CORE_OBJS} core/mixer/mixer_sse.cpp)
+    set(CPU_EXTS "${CPU_EXTS}, SSE")
+endif()
 if(HAVE_SSE2)
-    set(CORE_OBJS  ${CORE_OBJS} core/mixer/mixer_sse.cpp core/mixer/mixer_sse2.cpp)
-    set(CPU_EXTS "${CPU_EXTS}, SSE, SSE2")
+    set(CORE_OBJS  ${CORE_OBJS} core/mixer/mixer_sse2.cpp)
+    set(CPU_EXTS "${CPU_EXTS}, SSE2")
 endif()
 if(HAVE_SSE3)
     set(CORE_OBJS  ${CORE_OBJS} core/mixer/mixer_sse3.cpp)
@@ -778,6 +828,7 @@ endif()
 
 set(HAVE_ALSA       0)
 set(HAVE_OSS        0)
+set(HAVE_PIPEWIRE   0)
 set(HAVE_SOLARIS    0)
 set(HAVE_SNDIO      0)
 set(HAVE_DSOUND     0)
@@ -849,6 +900,25 @@ if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS)
     message(FATAL_ERROR "Failed to enabled required OSS backend")
 endif()
 
+# Check PipeWire backend
+option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF)
+if(PkgConfig_FOUND)
+    pkg_check_modules(PIPEWIRE libpipewire-0.3)
+    if(PIPEWIRE_FOUND)
+        option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON)
+        if(ALSOFT_BACKEND_PIPEWIRE)
+            set(HAVE_PIPEWIRE 1)
+            set(BACKENDS  "${BACKENDS} PipeWire${IS_LINKED},")
+            set(ALC_OBJS  ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h)
+            add_backend_libs(${PIPEWIRE_LIBRARIES})
+            set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS})
+        endif()
+    endif()
+endif()
+if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE)
+    message(FATAL_ERROR "Failed to enabled required PipeWire backend")
+endif()
+
 # Check Solaris backend
 option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF)
 find_package(AudioIO)
@@ -1148,10 +1218,16 @@ if(ALSOFT_EMBED_HRTF_DATA)
 endif()
 
 
-if(ALSOFT_UTILS AND NOT ALSOFT_NO_CONFIG_UTIL)
-    find_package(Qt5Widgets)
+if(ALSOFT_UTILS)
+    find_package(MySOFA)
+    if(NOT ALSOFT_NO_CONFIG_UTIL)
+        find_package(Qt5Widgets QUIET)
+        if(NOT Qt5Widgets_FOUND)
+            message(STATUS "Could NOT find Qt5Widgets")
+        endif()
+    endif()
 endif()
-if(ALSOFT_EXAMPLES)
+if(ALSOFT_UTILS OR ALSOFT_EXAMPLES)
     find_package(SndFile)
     find_package(SDL2)
     if(SDL2_FOUND)
@@ -1212,6 +1288,7 @@ if(LIBTYPE STREQUAL "STATIC")
     add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS})
     target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC)
     target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
+
     if(WIN32)
         # This option is for static linking OpenAL Soft into another project
         # that already defines the IDs. It is up to that project to ensure all
@@ -1232,13 +1309,14 @@ else()
             router/al.cpp
         )
         target_compile_definitions(OpenAL
-            PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS})
+            PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}"
+            "AL_API=${EXPORT_DECL}" ${CPP_DEFS})
         target_compile_options(OpenAL PRIVATE ${C_FLAGS})
         target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS})
         target_include_directories(OpenAL
           PUBLIC
             $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
-            $<INSTALL_INTERFACE:include>
+            $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
           PRIVATE
             ${OpenAL_SOURCE_DIR}/common
             ${OpenAL_BINARY_DIR}
@@ -1300,17 +1378,17 @@ else()
     endif()
 endif()
 
-set(OPENAL_LIB_NAME ${IMPL_TARGET} PARENT_SCOPE)
-
 target_include_directories(${IMPL_TARGET}
   PUBLIC
     $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
-    $<INSTALL_INTERFACE:include>
+  INTERFACE
+    $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include/AL>
+    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/AL>
   PRIVATE
     ${INC_PATHS}
     ${OpenAL_BINARY_DIR}
     ${OpenAL_SOURCE_DIR}
-    ${OpenAL_SOURCE_DIR}/alc
     ${OpenAL_SOURCE_DIR}/common
 )
 
@@ -1319,7 +1397,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME}
     SOVERSION ${LIB_MAJOR_VERSION}
 )
 target_compile_definitions(${IMPL_TARGET}
-    PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS})
+    PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}"
+    ${CPP_DEFS})
 target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS})
 
 if(TARGET build_version)
@@ -1353,25 +1432,35 @@ if(HAS_ROUTER)
     message(STATUS "Building DLL router")
 endif()
 
-message(STATUS "
-Building OpenAL with support for the following backends:
-    ${BACKENDS}
-
-Building with support for CPU extensions:
-    ${CPU_EXTS}
-")
+message(STATUS "")
+message(STATUS "Building OpenAL with support for the following backends:")
+message(STATUS "   ${BACKENDS}")
+message(STATUS "")
+message(STATUS "Building with support for CPU extensions:")
+message(STATUS "    ${CPU_EXTS}")
+message(STATUS "")
 if(FPMATH_SET)
-    message(STATUS "Building with SSE${FPMATH_SET} codegen
-")
+    message(STATUS "Building with SSE${FPMATH_SET} codegen")
+    message(STATUS "")
+endif()
+
+if(ALSOFT_EAX)
+    message(STATUS "Building with legacy EAX extension support")
+    message(STATUS "")
 endif()
 
 if(ALSOFT_EMBED_HRTF_DATA)
-    message(STATUS "Embedding HRTF datasets
-")
+    message(STATUS "Embedding HRTF datasets")
+    message(STATUS "")
 endif()
 
+# An alias for sub-project builds
+add_library(OpenAL::OpenAL ALIAS OpenAL)
+
 # Install main library
 if(ALSOFT_INSTALL)
+    configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake
+        INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL)
     install(TARGETS OpenAL EXPORT OpenAL
         RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
         LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
@@ -1380,15 +1469,17 @@ if(ALSOFT_INSTALL)
         INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL)
     export(TARGETS OpenAL
         NAMESPACE OpenAL::
-        FILE OpenALConfig.cmake)
+        FILE OpenALTargets.cmake)
     install(EXPORT OpenAL
         DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL
         NAMESPACE OpenAL::
-        FILE OpenALConfig.cmake)
+        FILE OpenALTargets.cmake)
     install(DIRECTORY include/AL
         DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
     install(FILES "${OpenAL_BINARY_DIR}/openal.pc"
         DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+    install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake"
+        DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL")
     if(TARGET soft_oal)
         install(TARGETS soft_oal
             RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -1431,7 +1522,24 @@ if(ALSOFT_UTILS)
         set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info)
     endif()
 
-    find_package(MySOFA)
+    if(SNDFILE_FOUND)
+        add_executable(uhjdecoder utils/uhjdecoder.cpp)
+        target_compile_definitions(uhjdecoder PRIVATE ${CPP_DEFS})
+        target_include_directories(uhjdecoder
+            PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
+        target_compile_options(uhjdecoder PRIVATE ${C_FLAGS})
+        target_link_libraries(uhjdecoder PUBLIC common
+            PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
+
+        add_executable(uhjencoder utils/uhjencoder.cpp)
+        target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS})
+        target_include_directories(uhjencoder
+            PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common)
+        target_compile_options(uhjencoder PRIVATE ${C_FLAGS})
+        target_link_libraries(uhjencoder PUBLIC common
+            PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG})
+    endif()
+
     if(MYSOFA_FOUND)
         set(SOFA_SUPPORT_SRCS
             utils/sofa-support.cpp
@@ -1600,7 +1708,3 @@ if(EXTRA_INSTALLS)
         LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
         ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()
-
-if(MEGA)
-    install(TARGETS ${IMPL_TARGET} RUNTIME DESTINATION . LIBRARY DESTINATION .)
-ENDIF()

+ 54 - 0
libs/openal-soft/ChangeLog

@@ -1,3 +1,57 @@
+openal-soft-1.22.0:
+
+    Implemented the ALC_SOFT_reopen_device extension. This allows for moving
+    devices to different outputs without losing object state.
+
+    Implemented the ALC_SOFT_output_mode extension.
+
+    Implemented the AL_SOFT_callback_buffer extension.
+
+    Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer
+    formats and Super Stereo processing.
+
+    Implemented the legacy EAX extensions. Enabled by default only on Windows.
+
+    Improved sound positioning stability when a source is near the listener.
+
+    Improved the default 5.1 output decoder.
+
+    Improved the high frequency response for the HRTF second-order ambisonic
+    decoder.
+
+    Improved SoundIO capture behavior.
+
+    Fixed UHJ output on NEON-capable CPUs.
+
+    Fixed redundant effect updates when setting an effect property to the
+    current value.
+
+    Fixed WASAPI capture using really low sample rates, and sources with very
+    high pitch shifts when using a bsinc resampler.
+
+    Added a PipeWire backend.
+
+    Added enumeration for the JACK and CoreAudio backends.
+
+    Added optional support for RTKit to get real-time priority. Only used as a
+    backup when pthread_setschedparam fails.
+
+    Added an option for JACK playback to render directly in the real-time
+    processing callback. For lower playback latency, on by default.
+
+    Added an option for custom JACK devices.
+
+    Added utilities to encode and decode UHJ audio files. Files are decoded to
+    the .amb format, and are encoded from libsndfile-compatible formats.
+
+    Added an in-progress extension to hold sources in a playing state when a
+    device disconnects. Allows devices to be reset or reopened and have sources
+    resume from where they left off.
+
+    Lowered the priority of the JACK backend. To avoid it getting picked when
+    PipeWire is providing JACK compatibility, since the JACK backend is less
+    robust with auto-configuration.
+
 openal-soft-1.21.1:
 
     Improved alext.h's detection of standard types.

+ 9 - 0
libs/openal-soft/OpenALConfig.cmake.in

@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.1)
+
+include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake")
+
+set(OPENAL_FOUND ON)
+set(OPENAL_INCLUDE_DIR $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_INCLUDE_DIRECTORIES>)
+set(OPENAL_LIBRARY $<LINK_ONLY:OpenAL::OpenAL>)
+set(OPENAL_DEFINITIONS $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_COMPILE_DEFINITIONS>)
+set(OPENAL_VERSION_STRING @PACKAGE_VERSION@)

+ 832 - 34
libs/openal-soft/al/auxeffectslot.cpp

@@ -36,20 +36,24 @@
 #include "AL/efx.h"
 
 #include "albit.h"
-#include "alcmain.h"
-#include "alcontext.h"
+#include "alc/alu.h"
+#include "alc/context.h"
+#include "alc/device.h"
+#include "alc/inprogext.h"
 #include "almalloc.h"
 #include "alnumeric.h"
 #include "alspan.h"
-#include "alu.h"
 #include "buffer.h"
 #include "core/except.h"
 #include "core/fpu_ctrl.h"
 #include "core/logging.h"
 #include "effect.h"
-#include "inprogext.h"
 #include "opthelpers.h"
 
+#ifdef ALSOFT_EAX
+#include "eax_exception.h"
+#include "eax_utils.h"
+#endif // ALSOFT_EAX
 
 namespace {
 
@@ -277,8 +281,9 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context)
         { return entry.FreeMask != 0; });
     auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
+    ASSUME(slidx < 64);
 
-    ALeffectslot *slot{::new(sublist->EffectSlots + slidx) ALeffectslot{}};
+    ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx)};
     aluInitEffectPanning(&slot->mSlot, context);
 
     /* Add 1 to avoid source ID 0. */
@@ -303,13 +308,15 @@ void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
 }
 
 
-#define DO_UPDATEPROPS() do {                                                 \
-    if(!context->mDeferUpdates.load(std::memory_order_acquire)                \
-        && slot->mState == SlotState::Playing)                                \
-        slot->updateProps(context.get());                                     \
-    else                                                                      \
-        slot->PropsClean.clear(std::memory_order_release);                    \
-} while(0)
+inline void UpdateProps(ALeffectslot *slot, ALCcontext *context)
+{
+    if(!context->mDeferUpdates && slot->mState == SlotState::Playing)
+    {
+        slot->updateProps(context);
+        return;
+    }
+    slot->mPropsDirty = true;
+}
 
 } // namespace
 
@@ -325,7 +332,7 @@ START_API_FUNC
     if UNLIKELY(n <= 0) return;
 
     std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     if(static_cast<ALuint>(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
     {
         context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
@@ -460,7 +467,7 @@ START_API_FUNC
     if(slot->mState == SlotState::Playing)
         return;
 
-    slot->PropsClean.test_and_set(std::memory_order_acq_rel);
+    slot->mPropsDirty = false;
     slot->updateProps(context.get());
 
     AddActiveEffectSlots({&slot, 1}, context.get());
@@ -491,7 +498,7 @@ START_API_FUNC
 
         if(slot->mState != SlotState::Playing)
         {
-            slot->PropsClean.test_and_set(std::memory_order_acq_rel);
+            slot->mPropsDirty = false;
             slot->updateProps(context.get());
         }
         slots[i] = slot;
@@ -571,14 +578,19 @@ START_API_FUNC
     switch(param)
     {
     case AL_EFFECTSLOT_EFFECT:
-        device = context->mDevice.get();
+        device = context->mALDevice.get();
 
         {
             std::lock_guard<std::mutex> ___{device->EffectLock};
             ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
-            if(!(value == 0 || effect != nullptr))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value);
-            err = slot->initEffect(effect, context.get());
+            if(effect)
+                err = slot->initEffect(effect->type, effect->Props, context.get());
+            else
+            {
+                if(value != 0)
+                    SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value);
+                err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get());
+            }
         }
         if UNLIKELY(err != AL_NO_ERROR)
         {
@@ -587,8 +599,12 @@ START_API_FUNC
         }
         if UNLIKELY(slot->mState == SlotState::Initial)
         {
+            slot->mPropsDirty = false;
+            slot->updateProps(context.get());
+
             AddActiveEffectSlots({&slot, 1}, context.get());
             slot->mState = SlotState::Playing;
+            return;
         }
         break;
 
@@ -596,6 +612,8 @@ START_API_FUNC
         if(!(value == AL_TRUE || value == AL_FALSE))
             SETERR_RETURN(context, AL_INVALID_VALUE,,
                 "Effect slot auxiliary send auto out of range");
+        if UNLIKELY(slot->AuxSendAuto == !!value)
+            return;
         slot->AuxSendAuto = !!value;
         break;
 
@@ -603,6 +621,8 @@ START_API_FUNC
         target = LookupEffectSlot(context.get(), static_cast<ALuint>(value));
         if(value && !target)
             SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID");
+        if UNLIKELY(slot->Target == target)
+            return;
         if(target)
         {
             ALeffectslot *checker{target};
@@ -631,12 +651,20 @@ START_API_FUNC
         break;
 
     case AL_BUFFER:
-        device = context->mDevice.get();
+        device = context->mALDevice.get();
 
         if(slot->mState == SlotState::Playing)
             SETERR_RETURN(context, AL_INVALID_OPERATION,,
                 "Setting buffer on playing effect slot %u", slot->id);
 
+        if(ALbuffer *buffer{slot->Buffer})
+        {
+            if UNLIKELY(buffer->id == static_cast<ALuint>(value))
+                return;
+        }
+        else if UNLIKELY(value == 0)
+            return;
+
         {
             std::lock_guard<std::mutex> ___{device->BufferLock};
             ALbuffer *buffer{};
@@ -668,7 +696,7 @@ START_API_FUNC
         SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x",
             param);
     }
-    DO_UPDATEPROPS();
+    UpdateProps(slot, context.get());
 }
 END_API_FUNC
 
@@ -720,6 +748,8 @@ START_API_FUNC
     case AL_EFFECTSLOT_GAIN:
         if(!(value >= 0.0f && value <= 1.0f))
             SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range");
+        if UNLIKELY(slot->Gain == value)
+            return;
         slot->Gain = value;
         break;
 
@@ -727,7 +757,7 @@ START_API_FUNC
         SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x",
             param);
     }
-    DO_UPDATEPROPS();
+    UpdateProps(slot, context.get());
 }
 END_API_FUNC
 
@@ -884,10 +914,8 @@ END_API_FUNC
 
 ALeffectslot::ALeffectslot()
 {
-    PropsClean.test_and_set(std::memory_order_relaxed);
-
     EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)};
-    assert(factory != nullptr);
+    if(!factory) throw std::runtime_error{"Failed to get null effect factory"};
 
     al::intrusive_ptr<EffectState> state{factory->create()};
     Effect.State = state;
@@ -915,9 +943,10 @@ ALeffectslot::~ALeffectslot()
         mSlot.mEffectState->release();
 }
 
-ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context)
+ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps,
+    ALCcontext *context)
 {
-    EffectSlotType newtype{EffectSlotTypeFromEnum(effect ? effect->type : AL_EFFECT_NULL)};
+    EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)};
     if(newtype != Effect.Type)
     {
         EffectStateFactory *factory{getFactoryByType(newtype)};
@@ -928,7 +957,7 @@ ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context)
         }
         al::intrusive_ptr<EffectState> state{factory->create()};
 
-        ALCdevice *device{context->mDevice.get()};
+        ALCdevice *device{context->mALDevice.get()};
         std::unique_lock<std::mutex> statelock{device->StateLock};
         state->mOutTarget = device->Dry.Buffer;
         {
@@ -937,12 +966,12 @@ ALenum ALeffectslot::initEffect(ALeffect *effect, ALCcontext *context)
         }
 
         Effect.Type = newtype;
-        Effect.Props = effect ? effect->Props : EffectProps{};
+        Effect.Props = effectProps;
 
         Effect.State = std::move(state);
     }
-    else if(effect)
-        Effect.Props = effect->Props;
+    else if(newtype != EffectSlotType::None)
+        Effect.Props = effectProps;
 
     /* Remove state references from old effect slot property updates. */
     EffectSlotProps *props{context->mFreeEffectslotProps.load()};
@@ -994,17 +1023,20 @@ void ALeffectslot::updateProps(ALCcontext *context)
 void UpdateAllEffectSlotProps(ALCcontext *context)
 {
     std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+#ifdef ALSOFT_EAX
+    if(context->has_eax())
+        context->eax_commit_fx_slots();
+#endif
     for(auto &sublist : context->mEffectSlotList)
     {
         uint64_t usemask{~sublist.FreeMask};
         while(usemask)
         {
             const int idx{al::countr_zero(usemask)};
-            ALeffectslot *slot{sublist.EffectSlots + idx};
             usemask &= ~(1_u64 << idx);
+            ALeffectslot *slot{sublist.EffectSlots + idx};
 
-            if(slot->mState != SlotState::Stopped
-                && slot->PropsClean.test_and_set(std::memory_order_acq_rel))
+            if(slot->mState != SlotState::Stopped && std::exchange(slot->mPropsDirty, false))
                 slot->updateProps(context);
         }
     }
@@ -1023,3 +1055,769 @@ EffectSlotSubList::~EffectSlotSubList()
     al_free(EffectSlots);
     EffectSlots = nullptr;
 }
+
+#ifdef ALSOFT_EAX
+namespace {
+
+class EaxFxSlotException :
+    public EaxException
+{
+public:
+	explicit EaxFxSlotException(
+		const char* message)
+		:
+		EaxException{"EAX_FX_SLOT", message}
+	{
+	}
+}; // EaxFxSlotException
+
+
+} // namespace
+
+
+void ALeffectslot::eax_initialize(
+    ALCcontext& al_context,
+    EaxFxSlotIndexValue index)
+{
+    eax_al_context_ = &al_context;
+
+    if (index >= EAX_MAX_FXSLOTS)
+    {
+        eax_fail("Index out of range.");
+    }
+
+    eax_fx_slot_index_ = index;
+
+    eax_initialize_eax();
+    eax_initialize_lock();
+    eax_initialize_effects();
+}
+
+const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept
+{
+    return eax_eax_fx_slot_;
+}
+
+void ALeffectslot::eax_ensure_is_unlocked() const
+{
+    if (eax_is_locked_)
+        eax_fail("Locked.");
+}
+
+void ALeffectslot::eax_validate_fx_slot_effect(
+    const GUID& eax_effect_id)
+{
+    eax_ensure_is_unlocked();
+
+    if (eax_effect_id != EAX_NULL_GUID &&
+        eax_effect_id != EAX_REVERB_EFFECT &&
+        eax_effect_id != EAX_AGCCOMPRESSOR_EFFECT &&
+        eax_effect_id != EAX_AUTOWAH_EFFECT &&
+        eax_effect_id != EAX_CHORUS_EFFECT &&
+        eax_effect_id != EAX_DISTORTION_EFFECT &&
+        eax_effect_id != EAX_ECHO_EFFECT &&
+        eax_effect_id != EAX_EQUALIZER_EFFECT &&
+        eax_effect_id != EAX_FLANGER_EFFECT &&
+        eax_effect_id != EAX_FREQUENCYSHIFTER_EFFECT &&
+        eax_effect_id != EAX_VOCALMORPHER_EFFECT &&
+        eax_effect_id != EAX_PITCHSHIFTER_EFFECT &&
+        eax_effect_id != EAX_RINGMODULATOR_EFFECT)
+    {
+        eax_fail("Unsupported EAX effect GUID.");
+    }
+}
+
+void ALeffectslot::eax_validate_fx_slot_volume(
+    long eax_volume)
+{
+    eax_validate_range<EaxFxSlotException>(
+        "Volume",
+        eax_volume,
+        EAXFXSLOT_MINVOLUME,
+        EAXFXSLOT_MAXVOLUME);
+}
+
+void ALeffectslot::eax_validate_fx_slot_lock(
+    long eax_lock)
+{
+    eax_ensure_is_unlocked();
+
+    eax_validate_range<EaxFxSlotException>(
+        "Lock",
+        eax_lock,
+        EAXFXSLOT_MINLOCK,
+        EAXFXSLOT_MAXLOCK);
+}
+
+void ALeffectslot::eax_validate_fx_slot_flags(
+    unsigned long eax_flags,
+    int eax_version)
+{
+    eax_validate_range<EaxFxSlotException>(
+        "Flags",
+        eax_flags,
+        0UL,
+        ~(eax_version == 4 ? EAX40FXSLOTFLAGS_RESERVED : EAX50FXSLOTFLAGS_RESERVED));
+}
+
+void ALeffectslot::eax_validate_fx_slot_occlusion(
+    long eax_occlusion)
+{
+    eax_validate_range<EaxFxSlotException>(
+        "Occlusion",
+        eax_occlusion,
+        EAXFXSLOT_MINOCCLUSION,
+        EAXFXSLOT_MAXOCCLUSION);
+}
+
+void ALeffectslot::eax_validate_fx_slot_occlusion_lf_ratio(
+    float eax_occlusion_lf_ratio)
+{
+    eax_validate_range<EaxFxSlotException>(
+        "Occlusion LF Ratio",
+        eax_occlusion_lf_ratio,
+        EAXFXSLOT_MINOCCLUSIONLFRATIO,
+        EAXFXSLOT_MAXOCCLUSIONLFRATIO);
+}
+
+void ALeffectslot::eax_validate_fx_slot_all(
+    const EAX40FXSLOTPROPERTIES& fx_slot,
+    int eax_version)
+{
+    eax_validate_fx_slot_effect(fx_slot.guidLoadEffect);
+    eax_validate_fx_slot_volume(fx_slot.lVolume);
+    eax_validate_fx_slot_lock(fx_slot.lLock);
+    eax_validate_fx_slot_flags(fx_slot.ulFlags, eax_version);
+}
+
+void ALeffectslot::eax_validate_fx_slot_all(
+    const EAX50FXSLOTPROPERTIES& fx_slot,
+    int eax_version)
+{
+    eax_validate_fx_slot_all(static_cast<const EAX40FXSLOTPROPERTIES&>(fx_slot), eax_version);
+
+    eax_validate_fx_slot_occlusion(fx_slot.lOcclusion);
+    eax_validate_fx_slot_occlusion_lf_ratio(fx_slot.flOcclusionLFRatio);
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+    const GUID& eax_effect_id)
+{
+    if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id)
+    {
+        return;
+    }
+
+    eax_eax_fx_slot_.guidLoadEffect = eax_effect_id;
+
+    eax_set_fx_slot_effect();
+}
+
+void ALeffectslot::eax_set_fx_slot_volume(
+    long eax_volume)
+{
+    if (eax_eax_fx_slot_.lVolume == eax_volume)
+    {
+        return;
+    }
+
+    eax_eax_fx_slot_.lVolume = eax_volume;
+
+    eax_set_fx_slot_volume();
+}
+
+void ALeffectslot::eax_set_fx_slot_lock(
+    long eax_lock)
+{
+    if (eax_eax_fx_slot_.lLock == eax_lock)
+    {
+        return;
+    }
+
+    eax_eax_fx_slot_.lLock = eax_lock;
+}
+
+void ALeffectslot::eax_set_fx_slot_flags(
+    unsigned long eax_flags)
+{
+    if (eax_eax_fx_slot_.ulFlags == eax_flags)
+    {
+        return;
+    }
+
+    eax_eax_fx_slot_.ulFlags = eax_flags;
+
+    eax_set_fx_slot_flags();
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion(
+    long eax_occlusion)
+{
+    if (eax_eax_fx_slot_.lOcclusion == eax_occlusion)
+    {
+        return false;
+    }
+
+    eax_eax_fx_slot_.lOcclusion = eax_occlusion;
+
+    return true;
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio(
+    float eax_occlusion_lf_ratio)
+{
+    if (eax_eax_fx_slot_.flOcclusionLFRatio == eax_occlusion_lf_ratio)
+    {
+        return false;
+    }
+
+    eax_eax_fx_slot_.flOcclusionLFRatio = eax_occlusion_lf_ratio;
+
+    return true;
+}
+
+void ALeffectslot::eax_set_fx_slot_all(
+    const EAX40FXSLOTPROPERTIES& eax_fx_slot)
+{
+    eax_set_fx_slot_effect(eax_fx_slot.guidLoadEffect);
+    eax_set_fx_slot_volume(eax_fx_slot.lVolume);
+    eax_set_fx_slot_lock(eax_fx_slot.lLock);
+    eax_set_fx_slot_flags(eax_fx_slot.ulFlags);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_all(
+    const EAX50FXSLOTPROPERTIES& eax_fx_slot)
+{
+    eax_set_fx_slot_all(static_cast<const EAX40FXSLOTPROPERTIES&>(eax_fx_slot));
+
+    const auto is_occlusion_modified = eax_set_fx_slot_occlusion(eax_fx_slot.lOcclusion);
+    const auto is_occlusion_lf_ratio_modified = eax_set_fx_slot_occlusion_lf_ratio(eax_fx_slot.flOcclusionLFRatio);
+
+    return is_occlusion_modified || is_occlusion_lf_ratio_modified;
+}
+
+void ALeffectslot::eax_unlock_legacy() noexcept
+{
+    assert(eax_fx_slot_index_ < 2);
+    eax_is_locked_ = false;
+    eax_eax_fx_slot_.lLock = EAXFXSLOT_UNLOCKED;
+}
+
+[[noreturn]]
+void ALeffectslot::eax_fail(
+    const char* message)
+{
+    throw EaxFxSlotException{message};
+}
+
+GUID ALeffectslot::eax_get_eax_default_effect_guid() const noexcept
+{
+    switch (eax_fx_slot_index_)
+    {
+        case 0: return EAX_REVERB_EFFECT;
+        case 1: return EAX_CHORUS_EFFECT;
+        default: return EAX_NULL_GUID;
+    }
+}
+
+long ALeffectslot::eax_get_eax_default_lock() const noexcept
+{
+    return eax_fx_slot_index_ < 2 ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED;
+}
+
+void ALeffectslot::eax_set_eax_fx_slot_defaults()
+{
+    eax_eax_fx_slot_.guidLoadEffect = eax_get_eax_default_effect_guid();
+    eax_eax_fx_slot_.lVolume = EAXFXSLOT_DEFAULTVOLUME;
+    eax_eax_fx_slot_.lLock = eax_get_eax_default_lock();
+    eax_eax_fx_slot_.ulFlags = EAX40FXSLOT_DEFAULTFLAGS;
+    eax_eax_fx_slot_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION;
+    eax_eax_fx_slot_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO;
+}
+
+void ALeffectslot::eax_initialize_eax()
+{
+    eax_set_eax_fx_slot_defaults();
+}
+
+void ALeffectslot::eax_initialize_lock()
+{
+    eax_is_locked_ = (eax_fx_slot_index_ < 2);
+}
+
+void ALeffectslot::eax_initialize_effects()
+{
+    eax_set_fx_slot_effect();
+}
+
+void ALeffectslot::eax_get_fx_slot_all(
+    const EaxEaxCall& eax_call) const
+{
+    switch (eax_call.get_version())
+    {
+        case 4:
+            eax_call.set_value<EaxFxSlotException, EAX40FXSLOTPROPERTIES>(eax_eax_fx_slot_);
+            break;
+
+        case 5:
+            eax_call.set_value<EaxFxSlotException, EAX50FXSLOTPROPERTIES>(eax_eax_fx_slot_);
+            break;
+
+        default:
+            eax_fail("Unsupported EAX version.");
+    }
+}
+
+void ALeffectslot::eax_get_fx_slot(
+    const EaxEaxCall& eax_call) const
+{
+    switch (eax_call.get_property_id())
+    {
+        case EAXFXSLOT_ALLPARAMETERS:
+            eax_get_fx_slot_all(eax_call);
+            break;
+
+        case EAXFXSLOT_LOADEFFECT:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.guidLoadEffect);
+            break;
+
+        case EAXFXSLOT_VOLUME:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lVolume);
+            break;
+
+        case EAXFXSLOT_LOCK:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lLock);
+            break;
+
+        case EAXFXSLOT_FLAGS:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.ulFlags);
+            break;
+
+        case EAXFXSLOT_OCCLUSION:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.lOcclusion);
+            break;
+
+        case EAXFXSLOT_OCCLUSIONLFRATIO:
+            eax_call.set_value<EaxFxSlotException>(eax_eax_fx_slot_.flOcclusionLFRatio);
+            break;
+
+        default:
+            eax_fail("Unsupported FX slot property id.");
+    }
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_get(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_set_id())
+    {
+        case EaxEaxCallPropertySetId::fx_slot:
+            eax_get_fx_slot(eax_call);
+            break;
+
+        case EaxEaxCallPropertySetId::fx_slot_effect:
+            eax_dispatch_effect(eax_call);
+            break;
+
+        default:
+            eax_fail("Unsupported property id.");
+    }
+
+    return false;
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+    ALenum al_effect_type)
+{
+    if(!IsValidEffectType(al_effect_type))
+        eax_fail("Unsupported effect.");
+
+    eax_effect_ = nullptr;
+    eax_effect_ = eax_create_eax_effect(al_effect_type);
+
+    eax_set_effect_slot_effect(*eax_effect_);
+}
+
+void ALeffectslot::eax_set_fx_slot_effect()
+{
+    auto al_effect_type = ALenum{};
+
+    if (false)
+    {
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_NULL_GUID)
+    {
+        al_effect_type = AL_EFFECT_NULL;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AUTOWAH_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_AUTOWAH;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_CHORUS_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_CHORUS;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AGCCOMPRESSOR_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_COMPRESSOR;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_DISTORTION_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_DISTORTION;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_REVERB_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_EAXREVERB;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_ECHO_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_ECHO;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_EQUALIZER_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_EQUALIZER;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FLANGER_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_FLANGER;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FREQUENCYSHIFTER_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_FREQUENCY_SHIFTER;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_PITCHSHIFTER_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_PITCH_SHIFTER;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_RINGMODULATOR_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_RING_MODULATOR;
+    }
+    else if (eax_eax_fx_slot_.guidLoadEffect == EAX_VOCALMORPHER_EFFECT)
+    {
+        al_effect_type = AL_EFFECT_VOCAL_MORPHER;
+    }
+    else
+    {
+        eax_fail("Unsupported effect.");
+    }
+
+    eax_set_fx_slot_effect(al_effect_type);
+}
+
+void ALeffectslot::eax_set_efx_effect_slot_gain()
+{
+    const auto gain = level_mb_to_gain(
+        static_cast<float>(clamp(
+            eax_eax_fx_slot_.lVolume,
+            EAXFXSLOT_MINVOLUME,
+            EAXFXSLOT_MAXVOLUME)));
+
+    eax_set_effect_slot_gain(gain);
+}
+
+void ALeffectslot::eax_set_fx_slot_volume()
+{
+    eax_set_efx_effect_slot_gain();
+}
+
+void ALeffectslot::eax_set_effect_slot_send_auto()
+{
+    eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0);
+}
+
+void ALeffectslot::eax_set_fx_slot_flags()
+{
+    eax_set_effect_slot_send_auto();
+}
+
+void ALeffectslot::eax_set_fx_slot_effect(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_effect_id =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::guidLoadEffect)>();
+
+    eax_validate_fx_slot_effect(eax_effect_id);
+    eax_set_fx_slot_effect(eax_effect_id);
+}
+
+void ALeffectslot::eax_set_fx_slot_volume(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_volume =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::lVolume)>();
+
+    eax_validate_fx_slot_volume(eax_volume);
+    eax_set_fx_slot_volume(eax_volume);
+}
+
+void ALeffectslot::eax_set_fx_slot_lock(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_lock =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::lLock)>();
+
+    eax_validate_fx_slot_lock(eax_lock);
+    eax_set_fx_slot_lock(eax_lock);
+}
+
+void ALeffectslot::eax_set_fx_slot_flags(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_flags =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX40FXSLOTPROPERTIES::ulFlags)>();
+
+    eax_validate_fx_slot_flags(eax_flags, eax_call.get_version());
+    eax_set_fx_slot_flags(eax_flags);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_occlusion =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX50FXSLOTPROPERTIES::lOcclusion)>();
+
+    eax_validate_fx_slot_occlusion(eax_occlusion);
+
+    return eax_set_fx_slot_occlusion(eax_occlusion);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio(
+    const EaxEaxCall& eax_call)
+{
+    const auto& eax_occlusion_lf_ratio =
+        eax_call.get_value<EaxFxSlotException, const decltype(EAX50FXSLOTPROPERTIES::flOcclusionLFRatio)>();
+
+    eax_validate_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio);
+
+    return eax_set_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio);
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set_fx_slot_all(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_version())
+    {
+        case 4:
+            {
+                const auto& eax_all =
+                    eax_call.get_value<EaxFxSlotException, const EAX40FXSLOTPROPERTIES>();
+
+                eax_validate_fx_slot_all(eax_all, eax_call.get_version());
+                eax_set_fx_slot_all(eax_all);
+
+                return false;
+            }
+
+        case 5:
+            {
+                const auto& eax_all =
+                    eax_call.get_value<EaxFxSlotException, const EAX50FXSLOTPROPERTIES>();
+
+                eax_validate_fx_slot_all(eax_all, eax_call.get_version());
+                return eax_set_fx_slot_all(eax_all);
+            }
+
+        default:
+            eax_fail("Unsupported EAX version.");
+    }
+}
+
+bool ALeffectslot::eax_set_fx_slot(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_id())
+    {
+        case EAXFXSLOT_NONE:
+            return false;
+
+        case EAXFXSLOT_ALLPARAMETERS:
+            return eax_set_fx_slot_all(eax_call);
+
+        case EAXFXSLOT_LOADEFFECT:
+            eax_set_fx_slot_effect(eax_call);
+            return false;
+
+        case EAXFXSLOT_VOLUME:
+            eax_set_fx_slot_volume(eax_call);
+            return false;
+
+        case EAXFXSLOT_LOCK:
+            eax_set_fx_slot_lock(eax_call);
+            return false;
+
+        case EAXFXSLOT_FLAGS:
+            eax_set_fx_slot_flags(eax_call);
+            return false;
+
+        case EAXFXSLOT_OCCLUSION:
+            return eax_set_fx_slot_occlusion(eax_call);
+
+        case EAXFXSLOT_OCCLUSIONLFRATIO:
+            return eax_set_fx_slot_occlusion_lf_ratio(eax_call);
+
+
+        default:
+            eax_fail("Unsupported FX slot property id.");
+    }
+}
+
+// [[nodiscard]]
+bool ALeffectslot::eax_set(const EaxEaxCall& eax_call)
+{
+    switch(eax_call.get_property_set_id())
+    {
+        case EaxEaxCallPropertySetId::fx_slot:
+            return eax_set_fx_slot(eax_call);
+
+        case EaxEaxCallPropertySetId::fx_slot_effect:
+            eax_dispatch_effect(eax_call);
+            break;
+
+        default:
+            eax_fail("Unsupported property id.");
+    }
+
+    return false;
+}
+
+void ALeffectslot::eax_dispatch_effect(const EaxEaxCall& eax_call)
+{ if(eax_effect_) eax_effect_->dispatch(eax_call); }
+
+void ALeffectslot::eax_apply_deferred()
+{
+    /* The other FXSlot properties (volume, effect, etc) aren't deferred? */
+
+    auto is_changed = false;
+    if(eax_effect_)
+        is_changed = eax_effect_->apply_deferred();
+    if(is_changed)
+        eax_set_effect_slot_effect(*eax_effect_);
+}
+
+
+void ALeffectslot::eax_set_effect_slot_effect(EaxEffect &effect)
+{
+#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] "
+
+    const auto error = initEffect(effect.al_effect_type_, effect.al_effect_props_, eax_al_context_);
+    if (error != AL_NO_ERROR)
+    {
+        ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect.");
+        return;
+    }
+
+    if (mState == SlotState::Initial)
+    {
+        mPropsDirty = false;
+        updateProps(eax_al_context_);
+
+        auto effect_slot_ptr = this;
+
+        AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_);
+        mState = SlotState::Playing;
+
+        return;
+    }
+
+    UpdateProps(this, eax_al_context_);
+
+#undef EAX_PREFIX
+}
+
+void ALeffectslot::eax_set_effect_slot_send_auto(
+    bool is_send_auto)
+{
+    if(AuxSendAuto == is_send_auto)
+        return;
+
+    AuxSendAuto = is_send_auto;
+    UpdateProps(this, eax_al_context_);
+}
+
+void ALeffectslot::eax_set_effect_slot_gain(
+    ALfloat gain)
+{
+#define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] "
+
+    if(gain == Gain)
+        return;
+    if(gain < 0.0f || gain > 1.0f)
+        ERR(EAX_PREFIX "Gain out of range (%f)\n", gain);
+
+    Gain = clampf(gain, 0.0f, 1.0f);
+    UpdateProps(this, eax_al_context_);
+
+#undef EAX_PREFIX
+}
+
+
+void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot)
+{
+    assert(effect_slot);
+    eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot);
+}
+
+
+EaxAlEffectSlotUPtr eax_create_al_effect_slot(
+    ALCcontext& context)
+{
+#define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] "
+
+    std::unique_lock<std::mutex> effect_slot_lock{context.mEffectSlotLock};
+
+    auto& device = *context.mALDevice;
+
+    if (context.mNumEffectSlots == device.AuxiliaryEffectSlotMax)
+    {
+        ERR(EAX_PREFIX "%s\n", "Out of memory.");
+        return nullptr;
+    }
+
+    if (!EnsureEffectSlots(&context, 1))
+    {
+        ERR(EAX_PREFIX "%s\n", "Failed to ensure.");
+        return nullptr;
+    }
+
+    auto effect_slot = EaxAlEffectSlotUPtr{AllocEffectSlot(&context)};
+    if (!effect_slot)
+    {
+        ERR(EAX_PREFIX "%s\n", "Failed to allocate.");
+        return nullptr;
+    }
+
+    return effect_slot;
+
+#undef EAX_PREFIX
+}
+
+void eax_delete_al_effect_slot(
+    ALCcontext& context,
+    ALeffectslot& effect_slot)
+{
+#define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] "
+
+    std::lock_guard<std::mutex> effect_slot_lock{context.mEffectSlotLock};
+
+    if (ReadRef(effect_slot.ref) != 0)
+    {
+        ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id);
+        return;
+    }
+
+    auto effect_slot_ptr = &effect_slot;
+
+    RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context);
+    FreeEffectSlot(&context, &effect_slot);
+
+#undef EAX_PREFIX
+}
+#endif // ALSOFT_EAX

+ 214 - 5
libs/openal-soft/al/auxeffectslot.h

@@ -8,14 +8,22 @@
 #include "AL/alc.h"
 #include "AL/efx.h"
 
-#include "alcmain.h"
+#include "alc/device.h"
+#include "alc/effects/base.h"
 #include "almalloc.h"
 #include "atomic.h"
-#include "effectslot.h"
-#include "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"
+#endif // ALSOFT_EAX
+
 struct ALbuffer;
 struct ALeffect;
 struct WetBuffer;
@@ -40,7 +48,7 @@ struct ALeffectslot {
         al::intrusive_ptr<EffectState> State;
     } Effect;
 
-    std::atomic_flag PropsClean;
+    bool mPropsDirty{true};
 
     SlotState mState{SlotState::Initial};
 
@@ -56,13 +64,214 @@ struct ALeffectslot {
     ALeffectslot& operator=(const ALeffectslot&) = delete;
     ~ALeffectslot();
 
-    ALenum initEffect(ALeffect *effect, ALCcontext *context);
+    ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
     void updateProps(ALCcontext *context);
 
     /* This can be new'd for the context's default effect slot. */
     DEF_NEWDEL(ALeffectslot)
+
+
+#ifdef ALSOFT_EAX
+public:
+    void eax_initialize(
+        ALCcontext& al_context,
+        EaxFxSlotIndexValue index);
+
+    const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept;
+
+
+    // [[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(); }
+
+private:
+    ALCcontext* eax_al_context_{};
+
+    EaxFxSlotIndexValue eax_fx_slot_index_{};
+
+    EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{};
+
+    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);
+
+
+    // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
+    void eax_set_effect_slot_effect(EaxEffect &effect);
+
+    // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)`
+    void eax_set_effect_slot_send_auto(bool is_send_auto);
+
+    // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)`
+    void eax_set_effect_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
+
 #endif

+ 352 - 122
libs/openal-soft/al/buffer.cpp

@@ -35,6 +35,7 @@
 #include <mutex>
 #include <new>
 #include <numeric>
+#include <stdexcept>
 #include <utility>
 
 #include "AL/al.h"
@@ -43,16 +44,23 @@
 
 #include "albit.h"
 #include "albyte.h"
-#include "alcmain.h"
-#include "alcontext.h"
+#include "alc/context.h"
+#include "alc/device.h"
+#include "alc/inprogext.h"
 #include "almalloc.h"
 #include "alnumeric.h"
 #include "aloptional.h"
 #include "atomic.h"
 #include "core/except.h"
-#include "inprogext.h"
+#include "core/logging.h"
+#include "core/voice.h"
 #include "opthelpers.h"
 
+#ifdef ALSOFT_EAX
+#include "eax_globals.h"
+#include "eax_x_ram.h"
+#endif // ALSOFT_EAX
+
 
 namespace {
 
@@ -110,10 +118,10 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t
 
     for(size_t c{0};c < numchans;c++)
     {
-        sample[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+        sample[c] = src[0] | (src[1]<<8);
         sample[c] = (sample[c]^0x8000) - 32768;
         src += 2;
-        index[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+        index[c] = src[0] | (src[1]<<8);
         index[c] = clampi((index[c]^0x8000) - 32768, 0, 88);
         src += 2;
 
@@ -126,8 +134,8 @@ void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t
         {
             for(size_t c{0};c < numchans;c++)
             {
-                code[c] = al::to_integer<ALuint>(src[0]) | (al::to_integer<ALuint>(src[1])<< 8) |
-                    (al::to_integer<ALuint>(src[2])<<16) | (al::to_integer<ALuint>(src[3])<<24);
+                code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16)
+                    | (ALuint{src[3]}<<24);
                 src += 4;
             }
         }
@@ -156,25 +164,23 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size
 
     for(size_t c{0};c < numchans;c++)
     {
-        blockpred[c] = std::min<ALubyte>(al::to_integer<ALubyte>(src[0]), 6);
+        blockpred[c] = std::min<ALubyte>(src[0], 6);
         ++src;
     }
     for(size_t c{0};c < numchans;c++)
     {
-        delta[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+        delta[c] = src[0] | (src[1]<<8);
         delta[c] = (delta[c]^0x8000) - 32768;
         src += 2;
     }
     for(size_t c{0};c < numchans;c++)
     {
-        samples[c][0] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
-            (al::to_integer<int>(src[1])<<8));
+        samples[c][0] = static_cast<ALshort>(src[0] | (src[1]<<8));
         src += 2;
     }
     for(size_t c{0};c < numchans;c++)
     {
-        samples[c][1] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
-            (al::to_integer<int>(src[1])<<8));
+        samples[c][1] = static_cast<ALshort>(src[0] | (src[1]<<8));
         src += 2;
     }
 
@@ -198,13 +204,13 @@ void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size
 
             int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] +
                 samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256};
-            pred += (al::to_integer<int>(nibble^0x08) - 0x08) * delta[c];
+            pred += ((nibble^0x08) - 0x08) * delta[c];
             pred  = clampi(pred, -32768, 32767);
 
             samples[c][1] = samples[c][0];
             samples[c][0] = static_cast<int16_t>(pred);
 
-            delta[c] = (MSADPCMAdaption[al::to_integer<ALubyte>(nibble)] * delta[c]) / 256;
+            delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256;
             delta[c] = maxi(16, delta[c]);
 
             *(dst++) = static_cast<int16_t>(pred);
@@ -271,6 +277,9 @@ ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept
     case UserFmtX71: return 8;
     case UserFmtBFormat2D: return (ambiorder*2) + 1;
     case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
+    case UserFmtUHJ2: return 2;
+    case UserFmtUHJ3: return 3;
+    case UserFmtUHJ4: return 4;
     }
     return 0;
 }
@@ -310,11 +319,82 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale)
     {
     case AmbiScaling::FuMa: return AL_FUMA_SOFT;
     case AmbiScaling::SN3D: return AL_SN3D_SOFT;
-    case AmbiScaling::N3D: return AL_SN3D_SOFT;
+    case AmbiScaling::N3D: return AL_N3D_SOFT;
+    case AmbiScaling::UHJ: break;
     }
     throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))};
 }
 
+al::optional<FmtChannels> FmtFromUserFmt(UserFmtChannels chans)
+{
+    switch(chans)
+    {
+    case UserFmtMono: return al::make_optional(FmtMono);
+    case UserFmtStereo: return al::make_optional(FmtStereo);
+    case UserFmtRear: return al::make_optional(FmtRear);
+    case UserFmtQuad: return al::make_optional(FmtQuad);
+    case UserFmtX51: return al::make_optional(FmtX51);
+    case UserFmtX61: return al::make_optional(FmtX61);
+    case UserFmtX71: return al::make_optional(FmtX71);
+    case UserFmtBFormat2D: return al::make_optional(FmtBFormat2D);
+    case UserFmtBFormat3D: return al::make_optional(FmtBFormat3D);
+    case UserFmtUHJ2: return al::make_optional(FmtUHJ2);
+    case UserFmtUHJ3: return al::make_optional(FmtUHJ3);
+    case UserFmtUHJ4: return al::make_optional(FmtUHJ4);
+    }
+    return al::nullopt;
+}
+al::optional<FmtType> FmtFromUserFmt(UserFmtType type)
+{
+    switch(type)
+    {
+    case UserFmtUByte: return al::make_optional(FmtUByte);
+    case UserFmtShort: return al::make_optional(FmtShort);
+    case UserFmtFloat: return al::make_optional(FmtFloat);
+    case UserFmtDouble: return al::make_optional(FmtDouble);
+    case UserFmtMulaw: return al::make_optional(FmtMulaw);
+    case UserFmtAlaw: return al::make_optional(FmtAlaw);
+    /* ADPCM not handled here. */
+    case UserFmtIMA4: break;
+    case UserFmtMSADPCM: break;
+    }
+    return al::nullopt;
+}
+
+
+#ifdef ALSOFT_EAX
+bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer,
+    const ALuint newsize) noexcept
+{
+    ALuint freemem{device.eax_x_ram_free_size};
+    /* If the buffer is currently in "hardware", add its memory to the free
+     * pool since it'll be "replaced".
+     */
+    if(buffer.eax_x_ram_is_hardware)
+        freemem += buffer.OriginalSize;
+    return freemem >= newsize;
+}
+
+void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept
+{
+    if(buffer.eax_x_ram_is_hardware)
+        return;
+
+    if(device.eax_x_ram_free_size >= buffer.OriginalSize)
+    {
+        device.eax_x_ram_free_size -= buffer.OriginalSize;
+        buffer.eax_x_ram_is_hardware = true;
+    }
+}
+
+void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer)
+{
+    if(al_buffer.eax_x_ram_is_hardware)
+        al_device.eax_x_ram_free_size += al_buffer.OriginalSize;
+    al_buffer.eax_x_ram_is_hardware = false;
+}
+#endif // ALSOFT_EAX
+
 
 constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT |
     AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)};
@@ -352,13 +432,12 @@ ALbuffer *AllocBuffer(ALCdevice *device)
 {
     auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
         [](const BufferSubList &entry) noexcept -> bool
-        { return entry.FreeMask != 0; }
-    );
-
+        { return entry.FreeMask != 0; });
     auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist));
     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
+    ASSUME(slidx < 64);
 
-    ALbuffer *buffer{::new (sublist->Buffers + slidx) ALbuffer{}};
+    ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)};
 
     /* Add 1 to avoid buffer ID 0. */
     buffer->id = ((lidx<<6) | slidx) + 1;
@@ -370,6 +449,10 @@ ALbuffer *AllocBuffer(ALCdevice *device)
 
 void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
 {
+#ifdef ALSOFT_EAX
+    eax_x_ram_clear(*device, *buffer);
+#endif // ALSOFT_EAX
+
     const ALuint id{buffer->id - 1};
     const size_t lidx{id >> 6};
     const ALuint slidx{id & 0x3f};
@@ -453,46 +536,26 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
                       ALBuf->id);
 
     /* Currently no channel configurations need to be converted. */
-    FmtChannels DstChannels{FmtMono};
-    switch(SrcChannels)
-    {
-    case UserFmtMono: DstChannels = FmtMono; break;
-    case UserFmtStereo: DstChannels = FmtStereo; break;
-    case UserFmtRear: DstChannels = FmtRear; break;
-    case UserFmtQuad: DstChannels = FmtQuad; break;
-    case UserFmtX51: DstChannels = FmtX51; break;
-    case UserFmtX61: DstChannels = FmtX61; break;
-    case UserFmtX71: DstChannels = FmtX71; break;
-    case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
-    case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
-    }
-    if UNLIKELY(static_cast<long>(SrcChannels) != static_cast<long>(DstChannels))
+    auto DstChannels = FmtFromUserFmt(SrcChannels);
+    if UNLIKELY(!DstChannels)
         SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
 
-    /* IMA4 and MSADPCM convert to 16-bit short. */
-    FmtType DstType{FmtUByte};
-    switch(SrcType)
-    {
-    case UserFmtUByte: DstType = FmtUByte; break;
-    case UserFmtShort: DstType = FmtShort; break;
-    case UserFmtFloat: DstType = FmtFloat; break;
-    case UserFmtDouble: DstType = FmtDouble; break;
-    case UserFmtAlaw: DstType = FmtAlaw; break;
-    case UserFmtMulaw: DstType = FmtMulaw; break;
-    case UserFmtIMA4: DstType = FmtShort; break;
-    case UserFmtMSADPCM: DstType = FmtShort; break;
-    }
-
-    /* TODO: Currently we can only map samples when they're not converted. To
+    /* IMA4 and MSADPCM convert to 16-bit short.
+     *
+     * TODO: Currently we can only map samples when they're not converted. To
      * allow it would need some kind of double-buffering to hold onto a copy of
      * the original data.
      */
     if((access&MAP_READ_WRITE_FLAGS))
     {
-        if UNLIKELY(static_cast<long>(SrcType) != static_cast<long>(DstType))
+        if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM)
             SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped",
                 NameFromUserFmtType(SrcType));
     }
+    auto DstType = (SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM)
+        ? al::make_optional(FmtShort) : FmtFromUserFmt(SrcType);
+    if UNLIKELY(!DstType)
+        SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
 
     const ALuint unpackalign{ALBuf->UnpackAlign};
     const ALuint align{SanitizeAlignment(SrcType, unpackalign)};
@@ -500,13 +563,13 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
         SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples",
             unpackalign, NameFromUserFmtType(SrcType));
 
-    const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
-        ALBuf->UnpackAmbiOrder : 0};
+    const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
+        (IsUHJ(*DstChannels) ? 1 : 0)};
 
     if((access&AL_PRESERVE_DATA_BIT_SOFT))
     {
         /* Can only preserve data with the same format and alignment. */
-        if UNLIKELY(ALBuf->mChannels != DstChannels || ALBuf->OriginalType != SrcType)
+        if UNLIKELY(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType)
             SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format");
         if UNLIKELY(ALBuf->OriginalAlign != align)
             SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment");
@@ -534,13 +597,23 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
     /* Convert the sample frames to the number of bytes needed for internal
      * storage.
      */
-    ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)};
-    ALuint FrameSize{NumChannels * BytesFromFmt(DstType)};
+    ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)};
+    ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)};
     if UNLIKELY(frames > std::numeric_limits<size_t>::max()/FrameSize)
         SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
             "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize);
     size_t newsize{static_cast<size_t>(frames) * FrameSize};
 
+#ifdef ALSOFT_EAX
+    if(ALBuf->eax_x_ram_mode == AL_STORAGE_HARDWARE)
+    {
+        ALCdevice &device = *context->mALDevice;
+        if(!eax_x_ram_check_availability(device, *ALBuf, size))
+            SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
+                "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size);
+    }
+#endif
+
     /* Round up to the next 16-byte multiple. This could reallocate only when
      * increasing or the new size is less than half the current, but then the
      * buffer's AL_SIZE would not be very reliable for accounting buffer memory
@@ -561,7 +634,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
 
     if(SrcType == UserFmtIMA4)
     {
-        assert(DstType == FmtShort);
+        assert(*DstType == FmtShort);
         if(SrcData != nullptr && !ALBuf->mData.empty())
             Convert_int16_ima4(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
                 NumChannels, frames, align);
@@ -569,7 +642,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
     }
     else if(SrcType == UserFmtMSADPCM)
     {
-        assert(DstType == FmtShort);
+        assert(*DstType == FmtShort);
         if(SrcData != nullptr && !ALBuf->mData.empty())
             Convert_int16_msadpcm(reinterpret_cast<int16_t*>(ALBuf->mData.data()), SrcData,
                 NumChannels, frames, align);
@@ -577,7 +650,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
     }
     else
     {
-        assert(static_cast<long>(SrcType) == static_cast<long>(DstType));
+        assert(DstType.has_value());
         if(SrcData != nullptr && !ALBuf->mData.empty())
             std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin());
         ALBuf->OriginalAlign = 1;
@@ -588,8 +661,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
     ALBuf->Access = access;
 
     ALBuf->mSampleRate = static_cast<ALuint>(freq);
-    ALBuf->mChannels = DstChannels;
-    ALBuf->mType = DstType;
+    ALBuf->mChannels = *DstChannels;
+    ALBuf->mType = *DstType;
     ALBuf->mAmbiOrder = ambiorder;
 
     ALBuf->mCallback = nullptr;
@@ -598,11 +671,16 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
     ALBuf->mSampleLen = frames;
     ALBuf->mLoopStart = 0;
     ALBuf->mLoopEnd = ALBuf->mSampleLen;
+
+#ifdef ALSOFT_EAX
+    if(eax_g_is_enabled && ALBuf->eax_x_ram_mode != AL_STORAGE_ACCESSIBLE)
+        eax_x_ram_apply(*context->mALDevice, *ALBuf);
+#endif
 }
 
 /** Prepares the buffer to use the specified callback, using the specified format. */
 void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
-    UserFmtChannels SrcChannels, UserFmtType SrcType, LPALBUFFERCALLBACKTYPESOFT callback,
+    UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback,
     void *userptr)
 {
     if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0)
@@ -610,43 +688,25 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
             ALBuf->id);
 
     /* Currently no channel configurations need to be converted. */
-    FmtChannels DstChannels{FmtMono};
-    switch(SrcChannels)
-    {
-    case UserFmtMono: DstChannels = FmtMono; break;
-    case UserFmtStereo: DstChannels = FmtStereo; break;
-    case UserFmtRear: DstChannels = FmtRear; break;
-    case UserFmtQuad: DstChannels = FmtQuad; break;
-    case UserFmtX51: DstChannels = FmtX51; break;
-    case UserFmtX61: DstChannels = FmtX61; break;
-    case UserFmtX71: DstChannels = FmtX71; break;
-    case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
-    case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
-    }
-    if UNLIKELY(static_cast<long>(SrcChannels) != static_cast<long>(DstChannels))
+    auto DstChannels = FmtFromUserFmt(SrcChannels);
+    if UNLIKELY(!DstChannels)
         SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format");
 
     /* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */
-    FmtType DstType{FmtUByte};
-    switch(SrcType)
-    {
-    case UserFmtUByte: DstType = FmtUByte; break;
-    case UserFmtShort: DstType = FmtShort; break;
-    case UserFmtFloat: DstType = FmtFloat; break;
-    case UserFmtDouble: DstType = FmtDouble; break;
-    case UserFmtAlaw: DstType = FmtAlaw; break;
-    case UserFmtMulaw: DstType = FmtMulaw; break;
-    case UserFmtIMA4: DstType = FmtShort; break;
-    case UserFmtMSADPCM: DstType = FmtShort; break;
-    }
-    if UNLIKELY(static_cast<long>(SrcType) != static_cast<long>(DstType))
+    auto DstType = FmtFromUserFmt(SrcType);
+    if UNLIKELY(!DstType)
         SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format");
 
-    const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
-        ALBuf->UnpackAmbiOrder : 0};
+    const ALuint ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder :
+        (IsUHJ(*DstChannels) ? 1 : 0)};
 
-    al::vector<al::byte,16>(FrameSizeFromFmt(DstChannels, DstType, ambiorder) *
-        size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData);
+    static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad};
+    al::vector<al::byte,16>(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) *
+        size_t{line_size}).swap(ALBuf->mData);
+
+#ifdef ALSOFT_EAX
+    eax_x_ram_clear(*context->mALDevice, *ALBuf);
+#endif
 
     ALBuf->mCallback = callback;
     ALBuf->mUserData = userptr;
@@ -657,8 +717,8 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
     ALBuf->Access = 0;
 
     ALBuf->mSampleRate = static_cast<ALuint>(freq);
-    ALBuf->mChannels = DstChannels;
-    ALBuf->mType = DstType;
+    ALBuf->mChannels = *DstChannels;
+    ALBuf->mType = *DstType;
     ALBuf->mAmbiOrder = ambiorder;
 
     ALBuf->mSampleLen = 0;
@@ -675,7 +735,7 @@ al::optional<DecompResult> DecomposeUserFormat(ALenum format)
         UserFmtChannels channels;
         UserFmtType type;
     };
-    static const std::array<FormatMap,46> UserFmtList{{
+    static const std::array<FormatMap,55> UserFmtList{{
         { AL_FORMAT_MONO8,             UserFmtMono, UserFmtUByte   },
         { AL_FORMAT_MONO16,            UserFmtMono, UserFmtShort   },
         { AL_FORMAT_MONO_FLOAT32,      UserFmtMono, UserFmtFloat   },
@@ -731,6 +791,18 @@ al::optional<DecompResult> DecomposeUserFormat(ALenum format)
         { AL_FORMAT_BFORMAT3D_16,      UserFmtBFormat3D, UserFmtShort },
         { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat },
         { AL_FORMAT_BFORMAT3D_MULAW,   UserFmtBFormat3D, UserFmtMulaw },
+
+        { AL_FORMAT_UHJ2CHN8_SOFT,        UserFmtUHJ2, UserFmtUByte },
+        { AL_FORMAT_UHJ2CHN16_SOFT,       UserFmtUHJ2, UserFmtShort },
+        { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, UserFmtUHJ2, UserFmtFloat },
+
+        { AL_FORMAT_UHJ3CHN8_SOFT,        UserFmtUHJ3, UserFmtUByte },
+        { AL_FORMAT_UHJ3CHN16_SOFT,       UserFmtUHJ3, UserFmtShort },
+        { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, UserFmtUHJ3, UserFmtFloat },
+
+        { AL_FORMAT_UHJ4CHN8_SOFT,        UserFmtUHJ4, UserFmtUByte },
+        { AL_FORMAT_UHJ4CHN16_SOFT,       UserFmtUHJ4, UserFmtShort },
+        { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, UserFmtUHJ4, UserFmtFloat },
     }};
 
     for(const auto &fmt : UserFmtList)
@@ -754,7 +826,7 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
     if UNLIKELY(n <= 0) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     if(!EnsureBuffers(device, static_cast<ALuint>(n)))
     {
@@ -794,7 +866,7 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n);
     if UNLIKELY(n <= 0) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     /* First try to find any buffers that are invalid or in-use. */
@@ -834,7 +906,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if LIKELY(context)
     {
-        ALCdevice *device{context->mDevice.get()};
+        ALCdevice *device{context->mALDevice.get()};
         std::lock_guard<std::mutex> _{device->BufferLock};
         if(!buffer || LookupBuffer(device, buffer))
             return AL_TRUE;
@@ -855,7 +927,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -877,8 +949,10 @@ START_API_FUNC
         if UNLIKELY(!usrfmt)
             context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
         else
+        {
             LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
                 usrfmt->type, static_cast<const al::byte*>(data), flags);
+        }
     }
 }
 END_API_FUNC
@@ -889,7 +963,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return nullptr;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -942,7 +1016,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -965,7 +1039,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -997,7 +1071,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -1127,7 +1201,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1147,7 +1221,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1166,7 +1240,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1188,7 +1262,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -1250,7 +1324,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1283,7 +1357,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -1321,7 +1395,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -1343,7 +1417,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1371,7 +1445,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
@@ -1393,7 +1467,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     ALbuffer *albuf = LookupBuffer(device, buffer);
     if UNLIKELY(!albuf)
@@ -1450,7 +1524,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@@ -1488,7 +1562,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     ALbuffer *albuf = LookupBuffer(device, buffer);
     if UNLIKELY(!albuf)
@@ -1510,13 +1584,13 @@ END_API_FUNC
 
 
 AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq,
-    LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags)
+    ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr)
 START_API_FUNC
 {
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
 
     ALbuffer *albuf = LookupBuffer(device, buffer);
@@ -1526,8 +1600,6 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
     else if UNLIKELY(callback == nullptr)
         context->setError(AL_INVALID_VALUE, "NULL callback");
-    else if UNLIKELY(flags != 0)
-        context->setError(AL_INVALID_VALUE, "Invalid callback flags 0x%x", flags);
     else
     {
         auto usrfmt = DecomposeUserFormat(format);
@@ -1546,7 +1618,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     ALbuffer *albuf = LookupBuffer(device, buffer);
     if UNLIKELY(!albuf)
@@ -1574,7 +1646,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@@ -1602,7 +1674,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->BufferLock};
     if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
         context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
@@ -1630,3 +1702,161 @@ BufferSubList::~BufferSubList()
     al_free(Buffers);
     Buffers = nullptr;
 }
+
+
+#ifdef ALSOFT_EAX
+FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value)
+START_API_FUNC
+{
+#define EAX_PREFIX "[EAXSetBufferMode] "
+
+    const auto context = ContextRef{GetContextRef()};
+    if(!context)
+    {
+        ERR(EAX_PREFIX "%s\n", "No current context.");
+        return ALC_FALSE;
+    }
+
+    if(!eax_g_is_enabled)
+    {
+        context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
+        return ALC_FALSE;
+    }
+
+    switch(value)
+    {
+    case AL_STORAGE_AUTOMATIC:
+    case AL_STORAGE_HARDWARE:
+    case AL_STORAGE_ACCESSIBLE:
+        break;
+
+    default:
+        context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value);
+        return ALC_FALSE;
+    }
+
+    if(n == 0)
+        return ALC_TRUE;
+
+    if(n < 0)
+    {
+        context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n);
+        return ALC_FALSE;
+    }
+
+    if(!buffers)
+    {
+        context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers");
+        return ALC_FALSE;
+    }
+
+    auto device = context->mALDevice.get();
+    std::lock_guard<std::mutex> device_lock{device->BufferLock};
+    size_t total_needed{0};
+
+    // Validate the buffers.
+    //
+    for(auto i = 0;i < n;++i)
+    {
+        const auto buffer = buffers[i];
+        if(buffer == AL_NONE)
+            continue;
+
+        const auto al_buffer = LookupBuffer(device, buffer);
+        if (!al_buffer)
+        {
+            ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer);
+            return ALC_FALSE;
+        }
+
+        /* TODO: Is the store location allowed to change for in-use buffers, or
+         * only when not set/queued on a source?
+         */
+
+        if(value == AL_STORAGE_HARDWARE && !al_buffer->eax_x_ram_is_hardware)
+        {
+            /* FIXME: This doesn't account for duplicate buffers. When the same
+             * buffer ID is specified multiple times in the provided list, it
+             * counts each instance as more memory that needs to fit in X-RAM.
+             */
+            if(unlikely(std::numeric_limits<size_t>::max()-al_buffer->OriginalSize < total_needed))
+            {
+                context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n",
+                    al_buffer->OriginalSize, total_needed);
+                return ALC_FALSE;
+            }
+            total_needed += al_buffer->OriginalSize;
+        }
+    }
+    if(total_needed > device->eax_x_ram_free_size)
+    {
+        context->setError(AL_INVALID_ENUM, EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)",
+            total_needed, device->eax_x_ram_free_size);
+        return ALC_FALSE;
+    }
+
+    // Update the mode.
+    //
+    for(auto i = 0;i < n;++i)
+    {
+        const auto buffer = buffers[i];
+        if(buffer == AL_NONE)
+            continue;
+
+        const auto al_buffer = LookupBuffer(device, buffer);
+        assert(al_buffer);
+
+        if(value != AL_STORAGE_ACCESSIBLE)
+            eax_x_ram_apply(*device, *al_buffer);
+        else
+            eax_x_ram_clear(*device, *al_buffer);
+        al_buffer->eax_x_ram_mode = value;
+    }
+
+    return AL_TRUE;
+
+#undef EAX_PREFIX
+}
+END_API_FUNC
+
+FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved)
+START_API_FUNC
+{
+#define EAX_PREFIX "[EAXGetBufferMode] "
+
+    const auto context = ContextRef{GetContextRef()};
+    if(!context)
+    {
+        ERR(EAX_PREFIX "%s\n", "No current context.");
+        return AL_NONE;
+    }
+
+    if(!eax_g_is_enabled)
+    {
+        context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled.");
+        return AL_NONE;
+    }
+
+    if(pReserved)
+    {
+        context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter");
+        return AL_NONE;
+    }
+
+    auto device = context->mALDevice.get();
+    std::lock_guard<std::mutex> device_lock{device->BufferLock};
+
+    const auto al_buffer = LookupBuffer(device, buffer);
+    if(!al_buffer)
+    {
+        context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer);
+        return AL_NONE;
+    }
+
+    return al_buffer->eax_x_ram_mode;
+
+#undef EAX_PREFIX
+}
+END_API_FUNC
+
+#endif // ALSOFT_EAX

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

@@ -6,12 +6,15 @@
 #include "AL/al.h"
 
 #include "albyte.h"
+#include "alc/inprogext.h"
 #include "almalloc.h"
 #include "atomic.h"
-#include "buffer_storage.h"
-#include "inprogext.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 {
@@ -35,6 +38,9 @@ enum UserFmtChannels : unsigned char {
     UserFmtX71 = FmtX71,
     UserFmtBFormat2D = FmtBFormat2D,
     UserFmtBFormat3D = FmtBFormat3D,
+    UserFmtUHJ2 = FmtUHJ2,
+    UserFmtUHJ3 = FmtUHJ3,
+    UserFmtUHJ4 = FmtUHJ4,
 };
 
 
@@ -65,6 +71,11 @@ struct ALbuffer : public BufferStorage {
     ALuint id{0};
 
     DISABLE_ALLOC()
+
+#ifdef ALSOFT_EAX
+    ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC};
+    bool eax_x_ram_is_hardware{};
+#endif // ALSOFT_EAX
 };
 
 #endif

+ 1213 - 0
libs/openal-soft/al/eax_api.cpp

@@ -0,0 +1,1213 @@
+//
+// EAX API.
+//
+// Based on headers `eax[2-5].h` included in Doom 3 source code:
+// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include
+//
+
+#include "config.h"
+
+#include <algorithm>
+
+#include "al/eax_api.h"
+
+
+const GUID DSPROPSETID_EAX_ReverbProperties =
+{
+    0x4A4E6FC1,
+    0xC341,
+    0x11D1,
+    {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
+};
+
+const GUID DSPROPSETID_EAXBUFFER_ReverbProperties =
+{
+    0x4A4E6FC0,
+    0xC341,
+    0x11D1,
+    {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
+};
+
+const GUID DSPROPSETID_EAX20_ListenerProperties =
+{
+    0x306A6A8,
+    0xB224,
+    0x11D2,
+    {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22}
+};
+
+const GUID DSPROPSETID_EAX20_BufferProperties =
+{
+    0x306A6A7,
+    0xB224,
+    0x11D2,
+    {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22}
+};
+
+const GUID DSPROPSETID_EAX30_ListenerProperties =
+{
+    0xA8FA6882,
+    0xB476,
+    0x11D3,
+    {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87}
+};
+
+const GUID DSPROPSETID_EAX30_BufferProperties =
+{
+    0xA8FA6881,
+    0xB476,
+    0x11D3,
+    {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87}
+};
+
+const GUID EAX_NULL_GUID =
+{
+    0x00000000,
+    0x0000,
+    0x0000,
+    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+const GUID EAX_PrimaryFXSlotID =
+{
+    0xF317866D,
+    0x924C,
+    0x450C,
+    {0x86, 0x1B, 0xE6, 0xDA, 0xA2, 0x5E, 0x7C, 0x20}
+};
+
+const GUID EAXPROPERTYID_EAX40_Context =
+{
+    0x1D4870AD,
+    0xDEF,
+    0x43C0,
+    {0xA4, 0xC, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42}
+};
+
+const GUID EAXPROPERTYID_EAX50_Context =
+{
+    0x57E13437,
+    0xB932,
+    0x4AB2,
+    {0xB8, 0xBD, 0x52, 0x66, 0xC1, 0xA8, 0x87, 0xEE}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot0 =
+{
+    0xC4D79F1E,
+    0xF1AC,
+    0x436B,
+    {0xA8, 0x1D, 0xA7, 0x38, 0xE7, 0x04, 0x54, 0x69}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot0 =
+{
+    0x91F9590F,
+    0xC388,
+    0x407A,
+    {0x84, 0xB0, 0x1B, 0xAE, 0xE, 0xF7, 0x1A, 0xBC}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot1 =
+{
+    0x8C00E96,
+    0x74BE,
+    0x4491,
+    {0x93, 0xAA, 0xE8, 0xAD, 0x35, 0xA4, 0x91, 0x17}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot1 =
+{
+    0x8F5F7ACA,
+    0x9608,
+    0x4965,
+    {0x81, 0x37, 0x82, 0x13, 0xC7, 0xB9, 0xD9, 0xDE}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot2 =
+{
+    0x1D433B88,
+    0xF0F6,
+    0x4637,
+    {0x91, 0x9F, 0x60, 0xE7, 0xE0, 0x6B, 0x5E, 0xDD}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot2 =
+{
+    0x3C0F5252,
+    0x9834,
+    0x46F0,
+    {0xA1, 0xD8, 0x5B, 0x95, 0xC4, 0xA0, 0xA, 0x30}
+};
+
+const GUID EAXPROPERTYID_EAX40_FXSlot3 =
+{
+    0xEFFF08EA,
+    0xC7D8,
+    0x44AB,
+    {0x93, 0xAD, 0x6D, 0xBD, 0x5F, 0x91, 0x00, 0x64}
+};
+
+const GUID EAXPROPERTYID_EAX50_FXSlot3 =
+{
+    0xE2EB0EAA,
+    0xE806,
+    0x45E7,
+    {0x9F, 0x86, 0x06, 0xC1, 0x57, 0x1A, 0x6F, 0xA3}
+};
+
+const GUID EAXPROPERTYID_EAX40_Source =
+{
+    0x1B86B823,
+    0x22DF,
+    0x4EAE,
+    {0x8B, 0x3C, 0x12, 0x78, 0xCE, 0x54, 0x42, 0x27}
+};
+
+const GUID EAXPROPERTYID_EAX50_Source =
+{
+    0x5EDF82F0,
+    0x24A7,
+    0x4F38,
+    {0x8E, 0x64, 0x2F, 0x09, 0xCA, 0x05, 0xDE, 0xE1}
+};
+
+const GUID EAX_REVERB_EFFECT =
+{
+    0xCF95C8F,
+    0xA3CC,
+    0x4849,
+    {0xB0, 0xB6, 0x83, 0x2E, 0xCC, 0x18, 0x22, 0xDF}
+};
+
+const GUID EAX_AGCCOMPRESSOR_EFFECT =
+{
+    0xBFB7A01E,
+    0x7825,
+    0x4039,
+    {0x92, 0x7F, 0x03, 0xAA, 0xBD, 0xA0, 0xC5, 0x60}
+};
+
+const GUID EAX_AUTOWAH_EFFECT =
+{
+    0xEC3130C0,
+    0xAC7A,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_CHORUS_EFFECT =
+{
+    0xDE6D6FE0,
+    0xAC79,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_DISTORTION_EFFECT =
+{
+    0x975A4CE0,
+    0xAC7E,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_ECHO_EFFECT =
+{
+    0xE9F1BC0,
+    0xAC82,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_EQUALIZER_EFFECT =
+{
+    0x65F94CE0,
+    0x9793,
+    0x11D3,
+    {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0}
+};
+
+const GUID EAX_FLANGER_EFFECT =
+{
+    0xA70007C0,
+    0x7D2,
+    0x11D3,
+    {0x9B, 0x1E, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_FREQUENCYSHIFTER_EFFECT =
+{
+    0xDC3E1880,
+    0x9212,
+    0x11D3,
+    {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0}
+};
+
+const GUID EAX_VOCALMORPHER_EFFECT =
+{
+    0xE41CF10C,
+    0x3383,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_PITCHSHIFTER_EFFECT =
+{
+    0xE7905100,
+    0xAFB2,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+const GUID EAX_RINGMODULATOR_EFFECT =
+{
+    0xB89FE60,
+    0xAFB5,
+    0x11D2,
+    {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1}
+};
+
+
+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;
+
+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
+{{
+    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,
+    EAX_PrimaryFXSlotID,
+    EAX_NULL_GUID,
+    EAX_NULL_GUID,
+}};
+
+
+const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS
+{{
+    EAX_NULL_GUID,
+    EAX_NULL_GUID,
+    EAX_NULL_GUID,
+    EAX_NULL_GUID,
+}};
+
+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);
+}
+
+
+namespace {
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC =
+{
+    EAXREVERB_DEFAULTENVIRONMENT,
+    EAXREVERB_DEFAULTENVIRONMENTSIZE,
+    EAXREVERB_DEFAULTENVIRONMENTDIFFUSION,
+    EAXREVERB_DEFAULTROOM,
+    EAXREVERB_DEFAULTROOMHF,
+    EAXREVERB_DEFAULTROOMLF,
+    EAXREVERB_DEFAULTDECAYTIME,
+    EAXREVERB_DEFAULTDECAYHFRATIO,
+    EAXREVERB_DEFAULTDECAYLFRATIO,
+    EAXREVERB_DEFAULTREFLECTIONS,
+    EAXREVERB_DEFAULTREFLECTIONSDELAY,
+    EAXREVERB_DEFAULTREFLECTIONSPAN,
+    EAXREVERB_DEFAULTREVERB,
+    EAXREVERB_DEFAULTREVERBDELAY,
+    EAXREVERB_DEFAULTREVERBPAN,
+    EAXREVERB_DEFAULTECHOTIME,
+    EAXREVERB_DEFAULTECHODEPTH,
+    EAXREVERB_DEFAULTMODULATIONTIME,
+    EAXREVERB_DEFAULTMODULATIONDEPTH,
+    EAXREVERB_DEFAULTAIRABSORPTIONHF,
+    EAXREVERB_DEFAULTHFREFERENCE,
+    EAXREVERB_DEFAULTLFREFERENCE,
+    EAXREVERB_DEFAULTROOMROLLOFFFACTOR,
+    EAXREVERB_DEFAULTFLAGS,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCELL =
+{
+    EAX_ENVIRONMENT_PADDEDCELL,
+    1.4F,
+    1.0F,
+    -1'000L,
+    -6'000L,
+    0L,
+    0.17F,
+    0.10F,
+    1.0F,
+    -1'204L,
+    0.001F,
+    EAXVECTOR{},
+    207L,
+    0.002F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM =
+{
+    EAX_ENVIRONMENT_ROOM,
+    1.9F,
+    1.0F,
+    -1'000L,
+    -454L,
+    0L,
+    0.40F,
+    0.83F,
+    1.0F,
+    -1'646L,
+    0.002F,
+    EAXVECTOR{},
+    53L,
+    0.003F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM =
+{
+    EAX_ENVIRONMENT_BATHROOM,
+    1.4F,
+    1.0F,
+    -1'000L,
+    -1'200L,
+    0L,
+    1.49F,
+    0.54F,
+    1.0F,
+    -370L,
+    0.007F,
+    EAXVECTOR{},
+    1'030L,
+    0.011F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM =
+{
+    EAX_ENVIRONMENT_LIVINGROOM,
+    2.5F,
+    1.0F,
+    -1'000L,
+    -6'000L,
+    0L,
+    0.50F,
+    0.10F,
+    1.0F,
+    -1'376,
+    0.003F,
+    EAXVECTOR{},
+    -1'104L,
+    0.004F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM =
+{
+    EAX_ENVIRONMENT_STONEROOM,
+    11.6F,
+    1.0F,
+    -1'000L,
+    -300L,
+    0L,
+    2.31F,
+    0.64F,
+    1.0F,
+    -711L,
+    0.012F,
+    EAXVECTOR{},
+    83L,
+    0.017F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM =
+{
+    EAX_ENVIRONMENT_AUDITORIUM,
+    21.6F,
+    1.0F,
+    -1'000L,
+    -476L,
+    0L,
+    4.32F,
+    0.59F,
+    1.0F,
+    -789L,
+    0.020F,
+    EAXVECTOR{},
+    -289L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL =
+{
+    EAX_ENVIRONMENT_CONCERTHALL,
+    19.6F,
+    1.0F,
+    -1'000L,
+    -500L,
+    0L,
+    3.92F,
+    0.70F,
+    1.0F,
+    -1'230L,
+    0.020F,
+    EAXVECTOR{},
+    -2L,
+    0.029F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE =
+{
+    EAX_ENVIRONMENT_CAVE,
+    14.6F,
+    1.0F,
+    -1'000L,
+    0L,
+    0L,
+    2.91F,
+    1.30F,
+    1.0F,
+    -602L,
+    0.015F,
+    EAXVECTOR{},
+    -302L,
+    0.022F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA =
+{
+    EAX_ENVIRONMENT_ARENA,
+    36.2F,
+    1.0F,
+    -1'000L,
+    -698L,
+    0L,
+    7.24F,
+    0.33F,
+    1.0F,
+    -1'166L,
+    0.020F,
+    EAXVECTOR{},
+    16L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR =
+{
+    EAX_ENVIRONMENT_HANGAR,
+    50.3F,
+    1.0F,
+    -1'000L,
+    -1'000L,
+    0L,
+    10.05F,
+    0.23F,
+    1.0F,
+    -602L,
+    0.020F,
+    EAXVECTOR{},
+    198L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY =
+{
+    EAX_ENVIRONMENT_CARPETEDHALLWAY,
+    1.9F,
+    1.0F,
+    -1'000L,
+    -4'000L,
+    0L,
+    0.30F,
+    0.10F,
+    1.0F,
+    -1'831L,
+    0.002F,
+    EAXVECTOR{},
+    -1'630L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY =
+{
+    EAX_ENVIRONMENT_HALLWAY,
+    1.8F,
+    1.0F,
+    -1'000L,
+    -300L,
+    0L,
+    1.49F,
+    0.59F,
+    1.0F,
+    -1'219L,
+    0.007F,
+    EAXVECTOR{},
+    441L,
+    0.011F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR =
+{
+    EAX_ENVIRONMENT_STONECORRIDOR,
+    13.5F,
+    1.0F,
+    -1'000L,
+    -237L,
+    0L,
+    2.70F,
+    0.79F,
+    1.0F,
+    -1'214L,
+    0.013F,
+    EAXVECTOR{},
+    395L,
+    0.020F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY =
+{
+    EAX_ENVIRONMENT_ALLEY,
+    7.5F,
+    0.300F,
+    -1'000L,
+    -270L,
+    0L,
+    1.49F,
+    0.86F,
+    1.0F,
+    -1'204L,
+    0.007F,
+    EAXVECTOR{},
+    -4L,
+    0.011F,
+    EAXVECTOR{},
+    0.125F,
+    0.950F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST =
+{
+    EAX_ENVIRONMENT_FOREST,
+    38.0F,
+    0.300F,
+    -1'000L,
+    -3'300L,
+    0L,
+    1.49F,
+    0.54F,
+    1.0F,
+    -2'560L,
+    0.162F,
+    EAXVECTOR{},
+    -229L,
+    0.088F,
+    EAXVECTOR{},
+    0.125F,
+    1.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY =
+{
+    EAX_ENVIRONMENT_CITY,
+    7.5F,
+    0.500F,
+    -1'000L,
+    -800L,
+    0L,
+    1.49F,
+    0.67F,
+    1.0F,
+    -2'273L,
+    0.007F,
+    EAXVECTOR{},
+    -1'691L,
+    0.011F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS =
+{
+    EAX_ENVIRONMENT_MOUNTAINS,
+    100.0F,
+    0.270F,
+    -1'000L,
+    -2'500L,
+    0L,
+    1.49F,
+    0.21F,
+    1.0F,
+    -2'780L,
+    0.300F,
+    EAXVECTOR{},
+    -1'434L,
+    0.100F,
+    EAXVECTOR{},
+    0.250F,
+    1.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY =
+{
+    EAX_ENVIRONMENT_QUARRY,
+    17.5F,
+    1.0F,
+    -1'000L,
+    -1'000L,
+    0L,
+    1.49F,
+    0.83F,
+    1.0F,
+    -10'000L,
+    0.061F,
+    EAXVECTOR{},
+    500L,
+    0.025F,
+    EAXVECTOR{},
+    0.125F,
+    0.700F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN =
+{
+    EAX_ENVIRONMENT_PLAIN,
+    42.5F,
+    0.210F,
+    -1'000L,
+    -2'000L,
+    0L,
+    1.49F,
+    0.50F,
+    1.0F,
+    -2'466L,
+    0.179F,
+    EAXVECTOR{},
+    -1'926L,
+    0.100F,
+    EAXVECTOR{},
+    0.250F,
+    1.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT =
+{
+    EAX_ENVIRONMENT_PARKINGLOT,
+    8.3F,
+    1.0F,
+    -1'000L,
+    0L,
+    0L,
+    1.65F,
+    1.50F,
+    1.0F,
+    -1'363L,
+    0.008F,
+    EAXVECTOR{},
+    -1'153L,
+    0.012F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE =
+{
+    EAX_ENVIRONMENT_SEWERPIPE,
+    1.7F,
+    0.800F,
+    -1'000L,
+    -1'000L,
+    0L,
+    2.81F,
+    0.14F,
+    1.0F,
+    429L,
+    0.014F,
+    EAXVECTOR{},
+    1'023L,
+    0.021F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    0.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER =
+{
+    EAX_ENVIRONMENT_UNDERWATER,
+    1.8F,
+    1.0F,
+    -1'000L,
+    -4'000L,
+    0L,
+    1.49F,
+    0.10F,
+    1.0F,
+    -449L,
+    0.007F,
+    EAXVECTOR{},
+    1'700L,
+    0.011F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    1.180F,
+    0.348F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x3FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED =
+{
+    EAX_ENVIRONMENT_DRUGGED,
+    1.9F,
+    0.500F,
+    -1'000L,
+    0L,
+    0L,
+    8.39F,
+    1.39F,
+    1.0F,
+    -115L,
+    0.002F,
+    EAXVECTOR{},
+    985L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    0.250F,
+    1.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY =
+{
+    EAX_ENVIRONMENT_DIZZY,
+    1.8F,
+    0.600F,
+    -1'000L,
+    -400L,
+    0L,
+    17.23F,
+    0.56F,
+    1.0F,
+    -1'713L,
+    0.020F,
+    EAXVECTOR{},
+    -613L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    1.0F,
+    0.810F,
+    0.310F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC =
+{
+    EAX_ENVIRONMENT_PSYCHOTIC,
+    1.0F,
+    0.500F,
+    -1'000L,
+    -151L,
+    0L,
+    7.56F,
+    0.91F,
+    1.0F,
+    -626L,
+    0.020F,
+    EAXVECTOR{},
+    774L,
+    0.030F,
+    EAXVECTOR{},
+    0.250F,
+    0.0F,
+    4.0F,
+    1.0F,
+    -5.0F,
+    5'000.0F,
+    250.0F,
+    0.0F,
+    0x1FUL,
+};
+
+} // namespace
+
+const EaxReverbPresets EAXREVERB_PRESETS{{
+    EAXREVERB_PRESET_GENERIC,
+    EAXREVERB_PRESET_PADDEDCELL,
+    EAXREVERB_PRESET_ROOM,
+    EAXREVERB_PRESET_BATHROOM,
+    EAXREVERB_PRESET_LIVINGROOM,
+    EAXREVERB_PRESET_STONEROOM,
+    EAXREVERB_PRESET_AUDITORIUM,
+    EAXREVERB_PRESET_CONCERTHALL,
+    EAXREVERB_PRESET_CAVE,
+    EAXREVERB_PRESET_ARENA,
+    EAXREVERB_PRESET_HANGAR,
+    EAXREVERB_PRESET_CARPETTEDHALLWAY,
+    EAXREVERB_PRESET_HALLWAY,
+    EAXREVERB_PRESET_STONECORRIDOR,
+    EAXREVERB_PRESET_ALLEY,
+    EAXREVERB_PRESET_FOREST,
+    EAXREVERB_PRESET_CITY,
+    EAXREVERB_PRESET_MOUNTAINS,
+    EAXREVERB_PRESET_QUARRY,
+    EAXREVERB_PRESET_PLAIN,
+    EAXREVERB_PRESET_PARKINGLOT,
+    EAXREVERB_PRESET_SEWERPIPE,
+    EAXREVERB_PRESET_UNDERWATER,
+    EAXREVERB_PRESET_DRUGGED,
+    EAXREVERB_PRESET_DIZZY,
+    EAXREVERB_PRESET_PSYCHOTIC,
+}};
+
+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,
+}};

+ 1557 - 0
libs/openal-soft/al/eax_api.h

@@ -0,0 +1,1557 @@
+#ifndef EAX_API_INCLUDED
+#define EAX_API_INCLUDED
+
+
+//
+// EAX API.
+//
+// Based on headers `eax[2-5].h` included in Doom 3 source code:
+// https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include
+//
+
+
+#include <cfloat>
+#include <cstdint>
+#include <cstring>
+
+#include <array>
+
+#include "AL/al.h"
+
+
+#ifndef GUID_DEFINED
+#define GUID_DEFINED
+typedef struct _GUID
+{
+    std::uint32_t Data1;
+    std::uint16_t Data2;
+    std::uint16_t Data3;
+    std::uint8_t Data4[8];
+} GUID;
+
+inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept
+{
+    return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0;
+}
+
+inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept
+{
+    return !(lhs == rhs);
+}
+#endif // GUID_DEFINED
+
+
+extern const GUID DSPROPSETID_EAX_ReverbProperties;
+
+enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int
+{
+    DSPROPERTY_EAX_ALL,
+    DSPROPERTY_EAX_ENVIRONMENT,
+    DSPROPERTY_EAX_VOLUME,
+    DSPROPERTY_EAX_DECAYTIME,
+    DSPROPERTY_EAX_DAMPING,
+}; // DSPROPERTY_EAX_REVERBPROPERTY
+
+struct EAX_REVERBPROPERTIES
+{
+    unsigned long environment;
+    float fVolume;
+    float fDecayTime_sec;
+    float fDamping;
+}; // EAX_REVERBPROPERTIES
+
+inline bool operator==(const EAX_REVERBPROPERTIES& lhs, const EAX_REVERBPROPERTIES& rhs) noexcept
+{
+    return std::memcmp(&lhs, &rhs, sizeof(EAX_REVERBPROPERTIES)) == 0;
+}
+
+extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties;
+
+enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int
+{
+    DSPROPERTY_EAXBUFFER_ALL,
+    DSPROPERTY_EAXBUFFER_REVERBMIX,
+}; // DSPROPERTY_EAXBUFFER_REVERBPROPERTY
+
+struct EAXBUFFER_REVERBPROPERTIES
+{
+    float fMix;
+};
+
+inline bool operator==(const EAXBUFFER_REVERBPROPERTIES& lhs, const EAXBUFFER_REVERBPROPERTIES& rhs) noexcept
+{
+    return lhs.fMix == rhs.fMix;
+}
+
+constexpr auto EAX_BUFFER_MINREVERBMIX = 0.0F;
+constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F;
+constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F;
+
+
+extern const GUID DSPROPSETID_EAX20_ListenerProperties;
+
+enum DSPROPERTY_EAX20_LISTENERPROPERTY :
+    unsigned int
+{
+    DSPROPERTY_EAX20LISTENER_NONE,
+    DSPROPERTY_EAX20LISTENER_ALLPARAMETERS,
+    DSPROPERTY_EAX20LISTENER_ROOM,
+    DSPROPERTY_EAX20LISTENER_ROOMHF,
+    DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR,
+    DSPROPERTY_EAX20LISTENER_DECAYTIME,
+    DSPROPERTY_EAX20LISTENER_DECAYHFRATIO,
+    DSPROPERTY_EAX20LISTENER_REFLECTIONS,
+    DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY,
+    DSPROPERTY_EAX20LISTENER_REVERB,
+    DSPROPERTY_EAX20LISTENER_REVERBDELAY,
+    DSPROPERTY_EAX20LISTENER_ENVIRONMENT,
+    DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE,
+    DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION,
+    DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF,
+    DSPROPERTY_EAX20LISTENER_FLAGS
+}; // DSPROPERTY_EAX20_LISTENERPROPERTY
+
+struct EAX20LISTENERPROPERTIES
+{
+    long lRoom; // room effect level at low frequencies
+    long lRoomHF; // room effect high-frequency level re. low frequency level
+    float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+    float flDecayTime; // reverberation decay time at low frequencies
+    float flDecayHFRatio; // high-frequency to low-frequency decay time ratio
+    long lReflections; // early reflections level relative to room effect
+    float flReflectionsDelay; // initial reflection delay time
+    long lReverb; // late reverberation level relative to room effect
+    float flReverbDelay; // late reverberation delay time relative to initial reflection
+    unsigned long dwEnvironment; // sets all listener properties
+    float flEnvironmentSize; // environment size in meters
+    float flEnvironmentDiffusion; // environment diffusion
+    float flAirAbsorptionHF; // change in level per meter at 5 kHz
+    unsigned long dwFlags; // modifies the behavior of properties
+}; // EAX20LISTENERPROPERTIES
+
+
+extern const GUID DSPROPSETID_EAX20_BufferProperties;
+
+
+enum DSPROPERTY_EAX20_BUFFERPROPERTY :
+    unsigned int
+{
+    DSPROPERTY_EAX20BUFFER_NONE,
+    DSPROPERTY_EAX20BUFFER_ALLPARAMETERS,
+    DSPROPERTY_EAX20BUFFER_DIRECT,
+    DSPROPERTY_EAX20BUFFER_DIRECTHF,
+    DSPROPERTY_EAX20BUFFER_ROOM,
+    DSPROPERTY_EAX20BUFFER_ROOMHF,
+    DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR,
+    DSPROPERTY_EAX20BUFFER_OBSTRUCTION,
+    DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO,
+    DSPROPERTY_EAX20BUFFER_OCCLUSION,
+    DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO,
+    DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO,
+    DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF,
+    DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR,
+    DSPROPERTY_EAX20BUFFER_FLAGS
+}; // DSPROPERTY_EAX20_BUFFERPROPERTY
+
+
+struct EAX20BUFFERPROPERTIES
+{
+    long lDirect; // direct path level
+    long lDirectHF; // direct path level at high frequencies
+    long lRoom; // room effect level
+    long lRoomHF; // room effect level at high frequencies
+    float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+    long lObstruction; // main obstruction control (attenuation at high frequencies) 
+    float flObstructionLFRatio; // obstruction low-frequency level re. main control
+    long lOcclusion; // main occlusion control (attenuation at high frequencies)
+    float flOcclusionLFRatio; // occlusion low-frequency level re. main control
+    float flOcclusionRoomRatio; // occlusion room effect level re. main control
+    long lOutsideVolumeHF; // outside sound cone level at high frequencies
+    float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF
+    unsigned long dwFlags; // modifies the behavior of properties
+}; // EAX20BUFFERPROPERTIES
+
+
+extern const GUID DSPROPSETID_EAX30_ListenerProperties;
+
+extern const GUID DSPROPSETID_EAX30_BufferProperties;
+
+
+constexpr auto EAX_MAX_FXSLOTS = 4;
+
+constexpr auto EAX40_MAX_ACTIVE_FXSLOTS = 2;
+constexpr auto EAX50_MAX_ACTIVE_FXSLOTS = 4;
+
+
+constexpr auto EAX_OK = 0L;
+constexpr auto EAXERR_INVALID_OPERATION = -1L;
+constexpr auto EAXERR_INVALID_VALUE = -2L;
+constexpr auto EAXERR_NO_EFFECT_LOADED = -3L;
+constexpr auto EAXERR_UNKNOWN_EFFECT = -4L;
+constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L;
+constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L;
+
+
+extern const GUID EAX_NULL_GUID;
+
+extern const GUID EAX_PrimaryFXSlotID;
+
+
+struct EAXVECTOR
+{
+    float x;
+    float y;
+    float z;
+}; // EAXVECTOR
+
+inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
+{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; }
+
+inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept
+{ return !(lhs == rhs); }
+
+
+extern const GUID EAXPROPERTYID_EAX40_Context;
+
+extern const GUID EAXPROPERTYID_EAX50_Context;
+
+// EAX50
+enum :
+    unsigned long
+{
+    HEADPHONES = 0,
+    SPEAKERS_2,
+    SPEAKERS_4,
+    SPEAKERS_5,	// 5.1 speakers
+    SPEAKERS_6, // 6.1 speakers
+    SPEAKERS_7, // 7.1 speakers
+};
+
+// EAX50
+enum :
+    unsigned long
+{
+    EAX_40 = 5, // EAX 4.0
+    EAX_50 = 6, // EAX 5.0
+};
+
+constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40;
+constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50;
+constexpr auto EAXCONTEXT_DEFAULTEAXSESSION = EAX_40;
+
+constexpr auto EAXCONTEXT_MINMAXACTIVESENDS = 2UL;
+constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL;
+constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL;
+
+// EAX50
+struct EAXSESSIONPROPERTIES
+{
+    unsigned long ulEAXVersion;
+    unsigned long ulMaxActiveSends;
+}; // EAXSESSIONPROPERTIES
+
+enum EAXCONTEXT_PROPERTY :
+    unsigned int
+{
+    EAXCONTEXT_NONE = 0,
+    EAXCONTEXT_ALLPARAMETERS,
+    EAXCONTEXT_PRIMARYFXSLOTID,
+    EAXCONTEXT_DISTANCEFACTOR,
+    EAXCONTEXT_AIRABSORPTIONHF,
+    EAXCONTEXT_HFREFERENCE,
+    EAXCONTEXT_LASTERROR,
+
+    // EAX50
+    EAXCONTEXT_SPEAKERCONFIG,
+    EAXCONTEXT_EAXSESSION,
+    EAXCONTEXT_MACROFXFACTOR,
+}; // EAXCONTEXT_PROPERTY
+
+struct EAX40CONTEXTPROPERTIES
+{
+    GUID guidPrimaryFXSlotID;
+    float flDistanceFactor;
+    float flAirAbsorptionHF;
+    float flHFReference;
+}; // EAX40CONTEXTPROPERTIES
+
+struct EAX50CONTEXTPROPERTIES :
+    public EAX40CONTEXTPROPERTIES
+{
+    float flMacroFXFactor;
+}; // EAX40CONTEXTPROPERTIES
+
+
+bool operator==(
+    const EAX40CONTEXTPROPERTIES& lhs,
+    const EAX40CONTEXTPROPERTIES& rhs) noexcept;
+
+bool operator==(
+    const EAX50CONTEXTPROPERTIES& lhs,
+    const EAX50CONTEXTPROPERTIES& rhs) noexcept;
+
+
+constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN;
+constexpr auto EAXCONTEXT_MAXDISTANCEFACTOR = FLT_MAX;
+constexpr auto EAXCONTEXT_DEFAULTDISTANCEFACTOR = 1.0F;
+
+constexpr auto EAXCONTEXT_MINAIRABSORPTIONHF = -100.0F;
+constexpr auto EAXCONTEXT_MAXAIRABSORPTIONHF = 0.0F;
+constexpr auto EAXCONTEXT_DEFAULTAIRABSORPTIONHF = -5.0F;
+
+constexpr auto EAXCONTEXT_MINHFREFERENCE = 1000.0F;
+constexpr auto EAXCONTEXT_MAXHFREFERENCE = 20000.0F;
+constexpr auto EAXCONTEXT_DEFAULTHFREFERENCE = 5000.0F;
+
+constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F;
+constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F;
+constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F;
+
+
+extern const GUID EAXPROPERTYID_EAX40_FXSlot0;
+
+extern const GUID EAXPROPERTYID_EAX50_FXSlot0;
+
+extern const GUID EAXPROPERTYID_EAX40_FXSlot1;
+
+extern const GUID EAXPROPERTYID_EAX50_FXSlot1;
+
+extern const GUID EAXPROPERTYID_EAX40_FXSlot2;
+
+extern const GUID EAXPROPERTYID_EAX50_FXSlot2;
+
+extern const GUID EAXPROPERTYID_EAX40_FXSlot3;
+
+extern const GUID EAXPROPERTYID_EAX50_FXSlot3;
+
+extern const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID;
+
+enum EAXFXSLOT_PROPERTY :
+    unsigned int
+{
+    EAXFXSLOT_PARAMETER = 0,
+
+    EAXFXSLOT_NONE = 0x10000,
+    EAXFXSLOT_ALLPARAMETERS,
+    EAXFXSLOT_LOADEFFECT,
+    EAXFXSLOT_VOLUME,
+    EAXFXSLOT_LOCK,
+    EAXFXSLOT_FLAGS,
+
+    // EAX50
+    EAXFXSLOT_OCCLUSION,
+    EAXFXSLOT_OCCLUSIONLFRATIO,
+}; // EAXFXSLOT_PROPERTY
+
+constexpr auto EAXFXSLOTFLAGS_ENVIRONMENT = 0x00000001UL;
+// EAX50
+constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002UL;
+
+constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFEUL; // reserved future use
+constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFCUL; // reserved future use
+
+
+constexpr auto EAXFXSLOT_MINVOLUME = -10'000L;
+constexpr auto EAXFXSLOT_MAXVOLUME = 0L;
+constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L;
+
+constexpr auto EAXFXSLOT_MINLOCK = 0L;
+constexpr auto EAXFXSLOT_MAXLOCK = 1L;
+
+enum :
+    long
+{
+    EAXFXSLOT_UNLOCKED = 0,
+    EAXFXSLOT_LOCKED = 1
+};
+
+constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000L;
+constexpr auto EAXFXSLOT_MAXOCCLUSION = 0L;
+constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0L;
+
+constexpr auto EAXFXSLOT_MINOCCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXFXSLOT_MAXOCCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO = 0.25F;
+
+constexpr auto EAX40FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT;
+
+constexpr auto EAX50FXSLOT_DEFAULTFLAGS =
+    EAXFXSLOTFLAGS_ENVIRONMENT |
+    EAXFXSLOTFLAGS_UPMIX; // ignored for reverb;
+
+struct EAX40FXSLOTPROPERTIES
+{
+    GUID guidLoadEffect;
+    long lVolume;
+    long lLock;
+    unsigned long ulFlags;
+}; // EAX40FXSLOTPROPERTIES
+
+struct EAX50FXSLOTPROPERTIES :
+    public EAX40FXSLOTPROPERTIES
+{
+    long lOcclusion;
+    float flOcclusionLFRatio;
+}; // EAX50FXSLOTPROPERTIES
+
+bool operator==(
+    const EAX40FXSLOTPROPERTIES& lhs,
+    const EAX40FXSLOTPROPERTIES& rhs) noexcept;
+
+bool operator==(
+    const EAX50FXSLOTPROPERTIES& lhs,
+    const EAX50FXSLOTPROPERTIES& rhs) noexcept;
+
+extern const GUID EAXPROPERTYID_EAX40_Source;
+
+extern const GUID EAXPROPERTYID_EAX50_Source;
+
+// Source object properties
+enum EAXSOURCE_PROPERTY :
+    unsigned int
+{
+    // EAX30
+
+    EAXSOURCE_NONE,
+    EAXSOURCE_ALLPARAMETERS,
+    EAXSOURCE_OBSTRUCTIONPARAMETERS,
+    EAXSOURCE_OCCLUSIONPARAMETERS,
+    EAXSOURCE_EXCLUSIONPARAMETERS,
+    EAXSOURCE_DIRECT,
+    EAXSOURCE_DIRECTHF,
+    EAXSOURCE_ROOM,
+    EAXSOURCE_ROOMHF,
+    EAXSOURCE_OBSTRUCTION,
+    EAXSOURCE_OBSTRUCTIONLFRATIO,
+    EAXSOURCE_OCCLUSION,
+    EAXSOURCE_OCCLUSIONLFRATIO,
+    EAXSOURCE_OCCLUSIONROOMRATIO,
+    EAXSOURCE_OCCLUSIONDIRECTRATIO,
+    EAXSOURCE_EXCLUSION,
+    EAXSOURCE_EXCLUSIONLFRATIO,
+    EAXSOURCE_OUTSIDEVOLUMEHF,
+    EAXSOURCE_DOPPLERFACTOR,
+    EAXSOURCE_ROLLOFFFACTOR,
+    EAXSOURCE_ROOMROLLOFFFACTOR,
+    EAXSOURCE_AIRABSORPTIONFACTOR,
+    EAXSOURCE_FLAGS,
+
+    // EAX40
+
+    EAXSOURCE_SENDPARAMETERS,
+    EAXSOURCE_ALLSENDPARAMETERS,
+    EAXSOURCE_OCCLUSIONSENDPARAMETERS,
+    EAXSOURCE_EXCLUSIONSENDPARAMETERS,
+    EAXSOURCE_ACTIVEFXSLOTID,
+
+    // EAX50
+
+    EAXSOURCE_MACROFXFACTOR,
+    EAXSOURCE_SPEAKERLEVELS,
+    EAXSOURCE_ALL2DPARAMETERS,
+}; // EAXSOURCE_PROPERTY
+
+
+constexpr auto EAXSOURCEFLAGS_DIRECTHFAUTO = 0x00000001UL; // relates to EAXSOURCE_DIRECTHF
+constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_ROOM
+constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF
+// EAX50
+constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL;
+// EAX50
+constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL;
+// EAX50
+constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL;
+
+constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use
+constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0UL; // reserved future use
+
+
+constexpr auto EAXSOURCE_MINSEND = -10'000L;
+constexpr auto EAXSOURCE_MAXSEND = 0L;
+constexpr auto EAXSOURCE_DEFAULTSEND = 0L;
+
+constexpr auto EAXSOURCE_MINSENDHF = -10'000L;
+constexpr auto EAXSOURCE_MAXSENDHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTSENDHF = 0L;
+
+constexpr auto EAXSOURCE_MINDIRECT = -10'000L;
+constexpr auto EAXSOURCE_MAXDIRECT = 1'000L;
+constexpr auto EAXSOURCE_DEFAULTDIRECT = 0L;
+
+constexpr auto EAXSOURCE_MINDIRECTHF = -10'000L;
+constexpr auto EAXSOURCE_MAXDIRECTHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0L;
+
+constexpr auto EAXSOURCE_MINROOM = -10'000L;
+constexpr auto EAXSOURCE_MAXROOM = 1'000L;
+constexpr auto EAXSOURCE_DEFAULTROOM = 0L;
+
+constexpr auto EAXSOURCE_MINROOMHF = -10'000L;
+constexpr auto EAXSOURCE_MAXROOMHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTROOMHF = 0L;
+
+constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000L;
+constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0L;
+constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0L;
+
+constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F;
+
+constexpr auto EAXSOURCE_MINOCCLUSION = -10'000L;
+constexpr auto EAXSOURCE_MAXOCCLUSION = 0L;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0L;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONLFRATIO = 0.25F;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONROOMRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONROOMRATIO = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO = 1.5F;
+
+constexpr auto EAXSOURCE_MINOCCLUSIONDIRECTRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXOCCLUSIONDIRECTRATIO = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO = 1.0F;
+
+constexpr auto EAXSOURCE_MINEXCLUSION = -10'000L;
+constexpr auto EAXSOURCE_MAXEXCLUSION = 0L;
+constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0L;
+
+constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F;
+constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F;
+
+constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000L;
+constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0L;
+constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0L;
+
+constexpr auto EAXSOURCE_MINDOPPLERFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXDOPPLERFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTDOPPLERFACTOR = 1.0F;
+
+constexpr auto EAXSOURCE_MINROLLOFFFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXROLLOFFFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAXSOURCE_MINROOMROLLOFFFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXROOMROLLOFFFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTROOMROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAXSOURCE_MINAIRABSORPTIONFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXAIRABSORPTIONFACTOR = 10.0F;
+constexpr auto EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR = 0.0F;
+
+// EAX50
+
+constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F;
+constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F;
+constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F;
+
+// EAX50
+
+constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L;
+constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L;
+constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L;
+
+constexpr auto EAXSOURCE_DEFAULTFLAGS =
+    EAXSOURCEFLAGS_DIRECTHFAUTO |
+    EAXSOURCEFLAGS_ROOMAUTO |
+    EAXSOURCEFLAGS_ROOMHFAUTO;
+
+enum :
+    long
+{
+    EAXSPEAKER_FRONT_LEFT = 1,
+    EAXSPEAKER_FRONT_CENTER = 2,
+    EAXSPEAKER_FRONT_RIGHT = 3,
+    EAXSPEAKER_SIDE_RIGHT = 4,
+    EAXSPEAKER_REAR_RIGHT = 5,
+    EAXSPEAKER_REAR_CENTER = 6,
+    EAXSPEAKER_REAR_LEFT = 7,
+    EAXSPEAKER_SIDE_LEFT = 8,
+    EAXSPEAKER_LOW_FREQUENCY = 9
+};
+
+// EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources
+// EAXSOURCEFLAGS_UPMIX is ignored for 3D sources
+constexpr auto EAX50SOURCE_DEFAULTFLAGS =
+    EAXSOURCEFLAGS_DIRECTHFAUTO |
+    EAXSOURCEFLAGS_ROOMAUTO |
+    EAXSOURCEFLAGS_ROOMHFAUTO |
+    EAXSOURCEFLAGS_UPMIX;
+
+struct EAX30SOURCEPROPERTIES
+{
+    long lDirect; // direct path level (at low and mid frequencies)
+    long lDirectHF; // relative direct path level at high frequencies
+    long lRoom; // room effect level (at low and mid frequencies)
+    long lRoomHF; // relative room effect level at high frequencies
+    long lObstruction; // main obstruction control (attenuation at high frequencies) 
+    float flObstructionLFRatio; // obstruction low-frequency level re. main control
+    long lOcclusion; // main occlusion control (attenuation at high frequencies)
+    float flOcclusionLFRatio; // occlusion low-frequency level re. main control
+    float flOcclusionRoomRatio; // relative occlusion control for room effect
+    float flOcclusionDirectRatio; // relative occlusion control for direct path
+    long lExclusion; // main exlusion control (attenuation at high frequencies)
+    float flExclusionLFRatio; // exclusion low-frequency level re. main control
+    long lOutsideVolumeHF; // outside sound cone level at high frequencies
+    float flDopplerFactor; // like DS3D flDopplerFactor but per source
+    float flRolloffFactor; // like DS3D flRolloffFactor but per source
+    float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+    float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF
+    unsigned long ulFlags; // modifies the behavior of properties
+}; // EAX30SOURCEPROPERTIES
+
+struct EAX50SOURCEPROPERTIES :
+    public EAX30SOURCEPROPERTIES
+{
+    float flMacroFXFactor;
+}; // EAX50SOURCEPROPERTIES
+
+struct EAXSOURCEALLSENDPROPERTIES
+{
+    GUID guidReceivingFXSlotID;
+    long lSend; // send level (at low and mid frequencies)
+    long lSendHF; // relative send level at high frequencies
+    long lOcclusion;
+    float flOcclusionLFRatio;
+    float flOcclusionRoomRatio;
+    float flOcclusionDirectRatio;
+    long lExclusion;
+    float flExclusionLFRatio;
+}; // EAXSOURCEALLSENDPROPERTIES
+
+struct EAXSOURCE2DPROPERTIES
+{
+    long lDirect; // direct path level (at low and mid frequencies)
+    long lDirectHF; // relative direct path level at high frequencies
+    long lRoom; // room effect level (at low and mid frequencies)
+    long lRoomHF; // relative room effect level at high frequencies
+    unsigned long ulFlags; // modifies the behavior of properties
+}; // EAXSOURCE2DPROPERTIES
+
+struct EAXSPEAKERLEVELPROPERTIES
+{
+    long lSpeakerID;
+    long lLevel;
+}; // EAXSPEAKERLEVELPROPERTIES
+
+struct EAX40ACTIVEFXSLOTS
+{
+    GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS];
+}; // EAX40ACTIVEFXSLOTS
+
+struct EAX50ACTIVEFXSLOTS
+{
+    GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS];
+}; // EAX50ACTIVEFXSLOTS
+
+bool operator==(
+    const EAX50ACTIVEFXSLOTS& lhs,
+    const EAX50ACTIVEFXSLOTS& rhs) noexcept;
+
+bool operator!=(
+    const EAX50ACTIVEFXSLOTS& lhs,
+    const EAX50ACTIVEFXSLOTS& rhs) noexcept;
+
+// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property.
+struct EAXOBSTRUCTIONPROPERTIES
+{
+    long lObstruction;
+    float flObstructionLFRatio;
+}; // EAXOBSTRUCTIONPROPERTIES
+
+// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property.
+struct EAXOCCLUSIONPROPERTIES
+{
+    long lOcclusion;
+    float flOcclusionLFRatio;
+    float flOcclusionRoomRatio;
+    float flOcclusionDirectRatio;
+}; // EAXOCCLUSIONPROPERTIES
+
+// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property.
+struct EAXEXCLUSIONPROPERTIES
+{
+    long lExclusion;
+    float flExclusionLFRatio;
+}; // EAXEXCLUSIONPROPERTIES
+
+// Use this structure for EAXSOURCE_SENDPARAMETERS properties.
+struct EAXSOURCESENDPROPERTIES
+{
+    GUID guidReceivingFXSlotID;
+    long lSend;
+    long lSendHF;
+}; // EAXSOURCESENDPROPERTIES
+
+// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS 
+struct EAXSOURCEOCCLUSIONSENDPROPERTIES
+{
+    GUID guidReceivingFXSlotID;
+    long lOcclusion;
+    float flOcclusionLFRatio;
+    float flOcclusionRoomRatio;
+    float flOcclusionDirectRatio;
+}; // EAXSOURCEOCCLUSIONSENDPROPERTIES
+
+// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS
+struct EAXSOURCEEXCLUSIONSENDPROPERTIES
+{
+    GUID guidReceivingFXSlotID;
+    long lExclusion;
+    float flExclusionLFRatio;
+}; // EAXSOURCEEXCLUSIONSENDPROPERTIES
+
+extern const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID;
+
+extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID;
+
+extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID;
+
+
+// EAX Reverb Effect
+
+extern const GUID EAX_REVERB_EFFECT;
+
+// Reverb effect properties
+enum EAXREVERB_PROPERTY :
+    unsigned int
+{
+    EAXREVERB_NONE,
+    EAXREVERB_ALLPARAMETERS,
+    EAXREVERB_ENVIRONMENT,
+    EAXREVERB_ENVIRONMENTSIZE,
+    EAXREVERB_ENVIRONMENTDIFFUSION,
+    EAXREVERB_ROOM,
+    EAXREVERB_ROOMHF,
+    EAXREVERB_ROOMLF,
+    EAXREVERB_DECAYTIME,
+    EAXREVERB_DECAYHFRATIO,
+    EAXREVERB_DECAYLFRATIO,
+    EAXREVERB_REFLECTIONS,
+    EAXREVERB_REFLECTIONSDELAY,
+    EAXREVERB_REFLECTIONSPAN,
+    EAXREVERB_REVERB,
+    EAXREVERB_REVERBDELAY,
+    EAXREVERB_REVERBPAN,
+    EAXREVERB_ECHOTIME,
+    EAXREVERB_ECHODEPTH,
+    EAXREVERB_MODULATIONTIME,
+    EAXREVERB_MODULATIONDEPTH,
+    EAXREVERB_AIRABSORPTIONHF,
+    EAXREVERB_HFREFERENCE,
+    EAXREVERB_LFREFERENCE,
+    EAXREVERB_ROOMROLLOFFFACTOR,
+    EAXREVERB_FLAGS,
+}; // EAXREVERB_PROPERTY
+
+// used by EAXREVERB_ENVIRONMENT
+enum :
+    unsigned long
+{
+    EAX_ENVIRONMENT_GENERIC,
+    EAX_ENVIRONMENT_PADDEDCELL,
+    EAX_ENVIRONMENT_ROOM,
+    EAX_ENVIRONMENT_BATHROOM,
+    EAX_ENVIRONMENT_LIVINGROOM,
+    EAX_ENVIRONMENT_STONEROOM,
+    EAX_ENVIRONMENT_AUDITORIUM,
+    EAX_ENVIRONMENT_CONCERTHALL,
+    EAX_ENVIRONMENT_CAVE,
+    EAX_ENVIRONMENT_ARENA,
+    EAX_ENVIRONMENT_HANGAR,
+    EAX_ENVIRONMENT_CARPETEDHALLWAY,
+    EAX_ENVIRONMENT_HALLWAY,
+    EAX_ENVIRONMENT_STONECORRIDOR,
+    EAX_ENVIRONMENT_ALLEY,
+    EAX_ENVIRONMENT_FOREST,
+    EAX_ENVIRONMENT_CITY,
+    EAX_ENVIRONMENT_MOUNTAINS,
+    EAX_ENVIRONMENT_QUARRY,
+    EAX_ENVIRONMENT_PLAIN,
+    EAX_ENVIRONMENT_PARKINGLOT,
+    EAX_ENVIRONMENT_SEWERPIPE,
+    EAX_ENVIRONMENT_UNDERWATER,
+    EAX_ENVIRONMENT_DRUGGED,
+    EAX_ENVIRONMENT_DIZZY,
+    EAX_ENVIRONMENT_PSYCHOTIC,
+
+    EAX1_ENVIRONMENT_COUNT,
+
+    // EAX30
+    EAX_ENVIRONMENT_UNDEFINED = EAX1_ENVIRONMENT_COUNT,
+
+    EAX3_ENVIRONMENT_COUNT,
+};
+
+
+// reverberation decay time
+constexpr auto EAXREVERBFLAGS_DECAYTIMESCALE = 0x00000001UL;
+
+// reflection level
+constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002UL;
+
+// initial reflection delay time
+constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL;
+
+// reflections level
+constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008UL;
+
+// late reverberation delay time
+constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010UL;
+
+// echo time
+// EAX30+
+constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040UL;
+
+// modulation time
+// EAX30+
+constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080UL;
+
+// This flag limits high-frequency decay time according to air absorption.
+constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL;
+
+constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use
+
+
+struct EAXREVERBPROPERTIES
+{
+    unsigned long ulEnvironment; // sets all reverb properties
+    float flEnvironmentSize; // environment size in meters
+    float flEnvironmentDiffusion; // environment diffusion
+    long lRoom; // room effect level (at mid frequencies)
+    long lRoomHF; // relative room effect level at high frequencies
+    long lRoomLF; // relative room effect level at low frequencies  
+    float flDecayTime; // reverberation decay time at mid frequencies
+    float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio
+    float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio   
+    long lReflections; // early reflections level relative to room effect
+    float flReflectionsDelay; // initial reflection delay time
+    EAXVECTOR vReflectionsPan; // early reflections panning vector
+    long lReverb; // late reverberation level relative to room effect
+    float flReverbDelay; // late reverberation delay time relative to initial reflection
+    EAXVECTOR vReverbPan; // late reverberation panning vector
+    float flEchoTime; // echo time
+    float flEchoDepth; // echo depth
+    float flModulationTime; // modulation time
+    float flModulationDepth; // modulation depth
+    float flAirAbsorptionHF; // change in level per meter at high frequencies
+    float flHFReference; // reference high frequency
+    float flLFReference; // reference low frequency 
+    float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect
+    unsigned long ulFlags; // modifies the behavior of properties
+}; // EAXREVERBPROPERTIES
+
+bool operator==(
+    const EAXREVERBPROPERTIES& lhs,
+    const EAXREVERBPROPERTIES& rhs) noexcept;
+
+bool operator!=(
+    const EAXREVERBPROPERTIES& lhs,
+    const EAXREVERBPROPERTIES& rhs) noexcept;
+
+
+constexpr auto EAXREVERB_MINENVIRONMENT = static_cast<unsigned long>(EAX_ENVIRONMENT_GENERIC);
+constexpr auto EAX1REVERB_MAXENVIRONMENT = static_cast<unsigned long>(EAX_ENVIRONMENT_PSYCHOTIC);
+constexpr auto EAX30REVERB_MAXENVIRONMENT = static_cast<unsigned long>(EAX_ENVIRONMENT_UNDEFINED);
+constexpr auto EAXREVERB_DEFAULTENVIRONMENT = static_cast<unsigned long>(EAX_ENVIRONMENT_GENERIC);
+
+constexpr auto EAXREVERB_MINENVIRONMENTSIZE = 1.0F;
+constexpr auto EAXREVERB_MAXENVIRONMENTSIZE = 100.0F;
+constexpr auto EAXREVERB_DEFAULTENVIRONMENTSIZE = 7.5F;
+
+constexpr auto EAXREVERB_MINENVIRONMENTDIFFUSION = 0.0F;
+constexpr auto EAXREVERB_MAXENVIRONMENTDIFFUSION = 1.0F;
+constexpr auto EAXREVERB_DEFAULTENVIRONMENTDIFFUSION = 1.0F;
+
+constexpr auto EAXREVERB_MINROOM = -10'000L;
+constexpr auto EAXREVERB_MAXROOM = 0L;
+constexpr auto EAXREVERB_DEFAULTROOM = -1'000L;
+
+constexpr auto EAXREVERB_MINROOMHF = -10'000L;
+constexpr auto EAXREVERB_MAXROOMHF = 0L;
+constexpr auto EAXREVERB_DEFAULTROOMHF = -100L;
+
+constexpr auto EAXREVERB_MINROOMLF = -10'000L;
+constexpr auto EAXREVERB_MAXROOMLF = 0L;
+constexpr auto EAXREVERB_DEFAULTROOMLF = 0L;
+
+constexpr auto EAXREVERB_MINDECAYTIME = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYTIME = 20.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYTIME = 1.49F;
+
+constexpr auto EAXREVERB_MINDECAYHFRATIO = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYHFRATIO = 2.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYHFRATIO = 0.83F;
+
+constexpr auto EAXREVERB_MINDECAYLFRATIO = 0.1F;
+constexpr auto EAXREVERB_MAXDECAYLFRATIO = 2.0F;
+constexpr auto EAXREVERB_DEFAULTDECAYLFRATIO = 1.0F;
+
+constexpr auto EAXREVERB_MINREFLECTIONS = -10'000L;
+constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000L;
+constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602L;
+
+constexpr auto EAXREVERB_MINREFLECTIONSDELAY = 0.0F;
+constexpr auto EAXREVERB_MAXREFLECTIONSDELAY = 0.3F;
+constexpr auto EAXREVERB_DEFAULTREFLECTIONSDELAY = 0.007F;
+
+constexpr auto EAXREVERB_DEFAULTREFLECTIONSPAN = EAXVECTOR{0.0F, 0.0F, 0.0F};
+
+constexpr auto EAXREVERB_MINREVERB = -10'000L;
+constexpr auto EAXREVERB_MAXREVERB = 2'000L;
+constexpr auto EAXREVERB_DEFAULTREVERB = 200L;
+
+constexpr auto EAXREVERB_MINREVERBDELAY = 0.0F;
+constexpr auto EAXREVERB_MAXREVERBDELAY = 0.1F;
+constexpr auto EAXREVERB_DEFAULTREVERBDELAY = 0.011F;
+
+constexpr auto EAXREVERB_DEFAULTREVERBPAN = EAXVECTOR{0.0F, 0.0F, 0.0F};
+
+constexpr auto EAXREVERB_MINECHOTIME = 0.075F;
+constexpr auto EAXREVERB_MAXECHOTIME = 0.25F;
+constexpr auto EAXREVERB_DEFAULTECHOTIME = 0.25F;
+
+constexpr auto EAXREVERB_MINECHODEPTH = 0.0F;
+constexpr auto EAXREVERB_MAXECHODEPTH = 1.0F;
+constexpr auto EAXREVERB_DEFAULTECHODEPTH = 0.0F;
+
+constexpr auto EAXREVERB_MINMODULATIONTIME = 0.04F;
+constexpr auto EAXREVERB_MAXMODULATIONTIME = 4.0F;
+constexpr auto EAXREVERB_DEFAULTMODULATIONTIME = 0.25F;
+
+constexpr auto EAXREVERB_MINMODULATIONDEPTH = 0.0F;
+constexpr auto EAXREVERB_MAXMODULATIONDEPTH = 1.0F;
+constexpr auto EAXREVERB_DEFAULTMODULATIONDEPTH = 0.0F;
+
+constexpr auto EAXREVERB_MINAIRABSORPTIONHF = -100.0F;
+constexpr auto EAXREVERB_MAXAIRABSORPTIONHF = 0.0F;
+constexpr auto EAXREVERB_DEFAULTAIRABSORPTIONHF = -5.0F;
+
+constexpr auto EAXREVERB_MINHFREFERENCE = 1'000.0F;
+constexpr auto EAXREVERB_MAXHFREFERENCE = 20'000.0F;
+constexpr auto EAXREVERB_DEFAULTHFREFERENCE = 5'000.0F;
+
+constexpr auto EAXREVERB_MINLFREFERENCE = 20.0F;
+constexpr auto EAXREVERB_MAXLFREFERENCE = 1'000.0F;
+constexpr auto EAXREVERB_DEFAULTLFREFERENCE = 250.0F;
+
+constexpr auto EAXREVERB_MINROOMROLLOFFFACTOR = 0.0F;
+constexpr auto EAXREVERB_MAXROOMROLLOFFFACTOR = 10.0F;
+constexpr auto EAXREVERB_DEFAULTROOMROLLOFFFACTOR = 0.0F;
+
+constexpr auto EAX1REVERB_MINVOLUME = 0.0F;
+constexpr auto EAX1REVERB_MAXVOLUME = 1.0F;
+
+constexpr auto EAX1REVERB_MINDAMPING = 0.0F;
+constexpr auto EAX1REVERB_MAXDAMPING = 2.0F;
+
+constexpr auto EAXREVERB_DEFAULTFLAGS =
+    EAXREVERBFLAGS_DECAYTIMESCALE |
+    EAXREVERBFLAGS_REFLECTIONSSCALE |
+    EAXREVERBFLAGS_REFLECTIONSDELAYSCALE |
+    EAXREVERBFLAGS_REVERBSCALE |
+    EAXREVERBFLAGS_REVERBDELAYSCALE |
+    EAXREVERBFLAGS_DECAYHFLIMIT;
+
+
+using EaxReverbPresets = std::array<EAXREVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
+extern const EaxReverbPresets EAXREVERB_PRESETS;
+
+
+using Eax1ReverbPresets = std::array<EAX_REVERBPROPERTIES, EAX1_ENVIRONMENT_COUNT>;
+extern const Eax1ReverbPresets EAX1REVERB_PRESETS;
+
+
+// AGC Compressor Effect
+
+extern const GUID EAX_AGCCOMPRESSOR_EFFECT;
+
+enum EAXAGCCOMPRESSOR_PROPERTY :
+    unsigned int
+{
+    EAXAGCCOMPRESSOR_NONE,
+    EAXAGCCOMPRESSOR_ALLPARAMETERS,
+    EAXAGCCOMPRESSOR_ONOFF,
+}; // EAXAGCCOMPRESSOR_PROPERTY
+
+struct EAXAGCCOMPRESSORPROPERTIES
+{
+    unsigned long ulOnOff; // Switch Compressor on or off
+}; // EAXAGCCOMPRESSORPROPERTIES
+
+
+constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0UL;
+constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1UL;
+constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF;
+
+
+// Autowah Effect
+
+extern const GUID EAX_AUTOWAH_EFFECT;
+
+enum EAXAUTOWAH_PROPERTY :
+    unsigned int
+{
+    EAXAUTOWAH_NONE,
+    EAXAUTOWAH_ALLPARAMETERS,
+    EAXAUTOWAH_ATTACKTIME,
+    EAXAUTOWAH_RELEASETIME,
+    EAXAUTOWAH_RESONANCE,
+    EAXAUTOWAH_PEAKLEVEL,
+}; // EAXAUTOWAH_PROPERTY
+
+struct EAXAUTOWAHPROPERTIES
+{
+    float flAttackTime; // Attack time (seconds)
+    float flReleaseTime; // Release time (seconds)
+    long lResonance; // Resonance (mB)
+    long lPeakLevel; // Peak level (mB)
+}; // EAXAUTOWAHPROPERTIES
+
+
+constexpr auto EAXAUTOWAH_MINATTACKTIME = 0.0001F;
+constexpr auto EAXAUTOWAH_MAXATTACKTIME = 1.0F;
+constexpr auto EAXAUTOWAH_DEFAULTATTACKTIME = 0.06F;
+
+constexpr auto EAXAUTOWAH_MINRELEASETIME = 0.0001F;
+constexpr auto EAXAUTOWAH_MAXRELEASETIME = 1.0F;
+constexpr auto EAXAUTOWAH_DEFAULTRELEASETIME = 0.06F;
+
+constexpr auto EAXAUTOWAH_MINRESONANCE = 600L;
+constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000L;
+constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000L;
+
+constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000L;
+constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000L;
+constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L;
+
+
+// Chorus Effect
+
+extern const GUID EAX_CHORUS_EFFECT;
+
+
+enum EAXCHORUS_PROPERTY :
+    unsigned int
+{
+    EAXCHORUS_NONE,
+    EAXCHORUS_ALLPARAMETERS,
+    EAXCHORUS_WAVEFORM,
+    EAXCHORUS_PHASE,
+    EAXCHORUS_RATE,
+    EAXCHORUS_DEPTH,
+    EAXCHORUS_FEEDBACK,
+    EAXCHORUS_DELAY,
+}; // EAXCHORUS_PROPERTY
+
+enum :
+    unsigned long
+{
+    EAX_CHORUS_SINUSOID,
+    EAX_CHORUS_TRIANGLE,
+};
+
+struct EAXCHORUSPROPERTIES
+{
+    unsigned long ulWaveform; // Waveform selector - see enum above
+    long lPhase; // Phase (Degrees)
+    float flRate; // Rate (Hz)
+    float flDepth; // Depth (0 to 1)
+    float flFeedback; // Feedback (-1 to 1)
+    float flDelay; // Delay (seconds)
+}; // EAXCHORUSPROPERTIES
+
+
+constexpr auto EAXCHORUS_MINWAVEFORM = 0UL;
+constexpr auto EAXCHORUS_MAXWAVEFORM = 1UL;
+constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1UL;
+
+constexpr auto EAXCHORUS_MINPHASE = -180L;
+constexpr auto EAXCHORUS_MAXPHASE = 180L;
+constexpr auto EAXCHORUS_DEFAULTPHASE = 90L;
+
+constexpr auto EAXCHORUS_MINRATE = 0.0F;
+constexpr auto EAXCHORUS_MAXRATE = 10.0F;
+constexpr auto EAXCHORUS_DEFAULTRATE = 1.1F;
+
+constexpr auto EAXCHORUS_MINDEPTH = 0.0F;
+constexpr auto EAXCHORUS_MAXDEPTH = 1.0F;
+constexpr auto EAXCHORUS_DEFAULTDEPTH = 0.1F;
+
+constexpr auto EAXCHORUS_MINFEEDBACK = -1.0F;
+constexpr auto EAXCHORUS_MAXFEEDBACK = 1.0F;
+constexpr auto EAXCHORUS_DEFAULTFEEDBACK = 0.25F;
+
+constexpr auto EAXCHORUS_MINDELAY = 0.0002F;
+constexpr auto EAXCHORUS_MAXDELAY = 0.016F;
+constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F;
+
+
+// Distortion Effect
+
+extern const GUID EAX_DISTORTION_EFFECT;
+
+enum EAXDISTORTION_PROPERTY :
+    unsigned int
+{
+    EAXDISTORTION_NONE,
+    EAXDISTORTION_ALLPARAMETERS,
+    EAXDISTORTION_EDGE,
+    EAXDISTORTION_GAIN,
+    EAXDISTORTION_LOWPASSCUTOFF,
+    EAXDISTORTION_EQCENTER,
+    EAXDISTORTION_EQBANDWIDTH,
+}; // EAXDISTORTION_PROPERTY
+
+
+struct EAXDISTORTIONPROPERTIES
+{
+    float flEdge; // Controls the shape of the distortion (0 to 1)
+    long lGain; // Controls the post distortion gain (mB)
+    float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz)
+    float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz)
+    float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz)
+}; // EAXDISTORTIONPROPERTIES
+
+
+constexpr auto EAXDISTORTION_MINEDGE = 0.0F;
+constexpr auto EAXDISTORTION_MAXEDGE = 1.0F;
+constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F;
+
+constexpr auto EAXDISTORTION_MINGAIN = -6000L;
+constexpr auto EAXDISTORTION_MAXGAIN = 0L;
+constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600L;
+
+constexpr auto EAXDISTORTION_MINLOWPASSCUTOFF = 80.0F;
+constexpr auto EAXDISTORTION_MAXLOWPASSCUTOFF = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTLOWPASSCUTOFF = 8000.0F;
+
+constexpr auto EAXDISTORTION_MINEQCENTER = 80.0F;
+constexpr auto EAXDISTORTION_MAXEQCENTER = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTEQCENTER = 3600.0F;
+
+constexpr auto EAXDISTORTION_MINEQBANDWIDTH = 80.0F;
+constexpr auto EAXDISTORTION_MAXEQBANDWIDTH = 24000.0F;
+constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F;
+
+
+// Echo Effect
+
+extern const GUID EAX_ECHO_EFFECT;
+
+
+enum EAXECHO_PROPERTY :
+    unsigned int
+{
+    EAXECHO_NONE,
+    EAXECHO_ALLPARAMETERS,
+    EAXECHO_DELAY,
+    EAXECHO_LRDELAY,
+    EAXECHO_DAMPING,
+    EAXECHO_FEEDBACK,
+    EAXECHO_SPREAD,
+}; // EAXECHO_PROPERTY
+
+
+struct EAXECHOPROPERTIES
+{
+    float flDelay; // Controls the initial delay time (seconds)
+    float flLRDelay; // Controls the delay time between the first and second taps (seconds)
+    float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1)
+    float flFeedback; // Controls the duration of echo repetition (0 to 1)
+    float flSpread; // Controls the left-right spread of the echoes
+}; // EAXECHOPROPERTIES
+
+
+constexpr auto EAXECHO_MINDAMPING = 0.0F;
+constexpr auto EAXECHO_MAXDAMPING = 0.99F;
+constexpr auto EAXECHO_DEFAULTDAMPING = 0.5F;
+
+constexpr auto EAXECHO_MINDELAY = 0.002F;
+constexpr auto EAXECHO_MAXDELAY = 0.207F;
+constexpr auto EAXECHO_DEFAULTDELAY = 0.1F;
+
+constexpr auto EAXECHO_MINLRDELAY = 0.0F;
+constexpr auto EAXECHO_MAXLRDELAY = 0.404F;
+constexpr auto EAXECHO_DEFAULTLRDELAY = 0.1F;
+
+constexpr auto EAXECHO_MINFEEDBACK = 0.0F;
+constexpr auto EAXECHO_MAXFEEDBACK = 1.0F;
+constexpr auto EAXECHO_DEFAULTFEEDBACK = 0.5F;
+
+constexpr auto EAXECHO_MINSPREAD = -1.0F;
+constexpr auto EAXECHO_MAXSPREAD = 1.0F;
+constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F;
+
+
+// Equalizer Effect
+
+extern const GUID EAX_EQUALIZER_EFFECT;
+
+
+enum EAXEQUALIZER_PROPERTY :
+    unsigned int
+{
+    EAXEQUALIZER_NONE,
+    EAXEQUALIZER_ALLPARAMETERS,
+    EAXEQUALIZER_LOWGAIN,
+    EAXEQUALIZER_LOWCUTOFF,
+    EAXEQUALIZER_MID1GAIN,
+    EAXEQUALIZER_MID1CENTER,
+    EAXEQUALIZER_MID1WIDTH,
+    EAXEQUALIZER_MID2GAIN,
+    EAXEQUALIZER_MID2CENTER,
+    EAXEQUALIZER_MID2WIDTH,
+    EAXEQUALIZER_HIGHGAIN,
+    EAXEQUALIZER_HIGHCUTOFF,
+}; // EAXEQUALIZER_PROPERTY
+
+
+struct EAXEQUALIZERPROPERTIES
+{
+    long lLowGain; // (mB)
+    float flLowCutOff; // (Hz)
+    long lMid1Gain; // (mB)
+    float flMid1Center; // (Hz)
+    float flMid1Width; // (octaves)
+    long lMid2Gain; // (mB)
+    float flMid2Center; // (Hz)
+    float flMid2Width; // (octaves)
+    long lHighGain; // (mB)
+    float flHighCutOff; // (Hz)
+}; // EAXEQUALIZERPROPERTIES
+
+
+constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F;
+constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F;
+constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F;
+
+constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINMID1CENTER = 200.0F;
+constexpr auto EAXEQUALIZER_MAXMID1CENTER = 3000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID1CENTER = 500.0F;
+
+constexpr auto EAXEQUALIZER_MINMID1WIDTH = 0.01F;
+constexpr auto EAXEQUALIZER_MAXMID1WIDTH = 1.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID1WIDTH = 1.0F;
+
+constexpr auto EAXEQUALIZER_MINMID2GAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINMID2CENTER = 1000.0F;
+constexpr auto EAXEQUALIZER_MAXMID2CENTER = 8000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID2CENTER = 3000.0F;
+
+constexpr auto EAXEQUALIZER_MINMID2WIDTH = 0.01F;
+constexpr auto EAXEQUALIZER_MAXMID2WIDTH = 1.0F;
+constexpr auto EAXEQUALIZER_DEFAULTMID2WIDTH = 1.0F;
+
+constexpr auto EAXEQUALIZER_MINHIGHGAIN = -1800L;
+constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800L;
+constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0L;
+
+constexpr auto EAXEQUALIZER_MINHIGHCUTOFF = 4000.0F;
+constexpr auto EAXEQUALIZER_MAXHIGHCUTOFF = 16000.0F;
+constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F;
+
+
+// Flanger Effect
+
+extern const GUID EAX_FLANGER_EFFECT;
+
+enum EAXFLANGER_PROPERTY :
+    unsigned int
+{
+    EAXFLANGER_NONE,
+    EAXFLANGER_ALLPARAMETERS,
+    EAXFLANGER_WAVEFORM,
+    EAXFLANGER_PHASE,
+    EAXFLANGER_RATE,
+    EAXFLANGER_DEPTH,
+    EAXFLANGER_FEEDBACK,
+    EAXFLANGER_DELAY,
+}; // EAXFLANGER_PROPERTY
+
+enum :
+    unsigned long
+{
+    EAX_FLANGER_SINUSOID,
+    EAX_FLANGER_TRIANGLE,
+};
+
+struct EAXFLANGERPROPERTIES
+{
+    unsigned long ulWaveform; // Waveform selector - see enum above
+    long lPhase; // Phase (Degrees)
+    float flRate; // Rate (Hz)
+    float flDepth; // Depth (0 to 1)
+    float flFeedback; // Feedback (0 to 1)
+    float flDelay; // Delay (seconds)
+}; // EAXFLANGERPROPERTIES
+
+
+constexpr auto EAXFLANGER_MINWAVEFORM = 0UL;
+constexpr auto EAXFLANGER_MAXWAVEFORM = 1UL;
+constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1UL;
+
+constexpr auto EAXFLANGER_MINPHASE = -180L;
+constexpr auto EAXFLANGER_MAXPHASE = 180L;
+constexpr auto EAXFLANGER_DEFAULTPHASE = 0L;
+
+constexpr auto EAXFLANGER_MINRATE = 0.0F;
+constexpr auto EAXFLANGER_MAXRATE = 10.0F;
+constexpr auto EAXFLANGER_DEFAULTRATE = 0.27F;
+
+constexpr auto EAXFLANGER_MINDEPTH = 0.0F;
+constexpr auto EAXFLANGER_MAXDEPTH = 1.0F;
+constexpr auto EAXFLANGER_DEFAULTDEPTH = 1.0F;
+
+constexpr auto EAXFLANGER_MINFEEDBACK = -1.0F;
+constexpr auto EAXFLANGER_MAXFEEDBACK = 1.0F;
+constexpr auto EAXFLANGER_DEFAULTFEEDBACK = -0.5F;
+
+constexpr auto EAXFLANGER_MINDELAY = 0.0002F;
+constexpr auto EAXFLANGER_MAXDELAY = 0.004F;
+constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F;
+
+
+// Frequency Shifter Effect
+
+extern const GUID EAX_FREQUENCYSHIFTER_EFFECT;
+
+enum EAXFREQUENCYSHIFTER_PROPERTY :
+    unsigned int
+{
+    EAXFREQUENCYSHIFTER_NONE,
+    EAXFREQUENCYSHIFTER_ALLPARAMETERS,
+    EAXFREQUENCYSHIFTER_FREQUENCY,
+    EAXFREQUENCYSHIFTER_LEFTDIRECTION,
+    EAXFREQUENCYSHIFTER_RIGHTDIRECTION,
+}; // EAXFREQUENCYSHIFTER_PROPERTY
+
+enum :
+    unsigned long
+{
+    EAX_FREQUENCYSHIFTER_DOWN,
+    EAX_FREQUENCYSHIFTER_UP,
+    EAX_FREQUENCYSHIFTER_OFF
+};
+
+struct EAXFREQUENCYSHIFTERPROPERTIES
+{
+    float flFrequency; // (Hz)
+    unsigned long ulLeftDirection; // see enum above
+    unsigned long ulRightDirection; // see enum above
+}; // EAXFREQUENCYSHIFTERPROPERTIES
+
+
+constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F;
+constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY;
+
+constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0UL;
+constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2UL;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION;
+
+constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0UL;
+constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2UL;
+constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION;
+
+
+// Vocal Morpher Effect
+
+extern const GUID EAX_VOCALMORPHER_EFFECT;
+
+enum EAXVOCALMORPHER_PROPERTY :
+    unsigned int
+{
+    EAXVOCALMORPHER_NONE,
+    EAXVOCALMORPHER_ALLPARAMETERS,
+    EAXVOCALMORPHER_PHONEMEA,
+    EAXVOCALMORPHER_PHONEMEACOARSETUNING,
+    EAXVOCALMORPHER_PHONEMEB,
+    EAXVOCALMORPHER_PHONEMEBCOARSETUNING,
+    EAXVOCALMORPHER_WAVEFORM,
+    EAXVOCALMORPHER_RATE,
+}; // EAXVOCALMORPHER_PROPERTY
+
+enum :
+    unsigned long
+{
+    A,
+    E,
+    I,
+    O,
+    U,
+    AA,
+    AE,
+    AH,
+    AO,
+    EH,
+    ER,
+    IH,
+    IY,
+    UH,
+    UW,
+    B,
+    D,
+    F,
+    G,
+    J,
+    K,
+    L,
+    M,
+    N,
+    P,
+    R,
+    S,
+    T,
+    V,
+    Z,
+};
+
+enum :
+    unsigned long
+{
+    EAX_VOCALMORPHER_SINUSOID,
+    EAX_VOCALMORPHER_TRIANGLE,
+    EAX_VOCALMORPHER_SAWTOOTH
+};
+
+// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS
+struct EAXVOCALMORPHERPROPERTIES
+{
+    unsigned long ulPhonemeA; // see enum above
+    long lPhonemeACoarseTuning; // (semitones)
+    unsigned long ulPhonemeB; // see enum above
+    long lPhonemeBCoarseTuning; // (semitones)
+    unsigned long ulWaveform; // Waveform selector - see enum above
+    float flRate; // (Hz)
+}; // EAXVOCALMORPHERPROPERTIES
+
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24L;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24L;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0L;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10UL;
+
+constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24L;
+constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24L;
+constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0L;
+
+constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0UL;
+constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2UL;
+constexpr auto EAXVOCALMORPHER_DEFAULTWAVEFORM = EAXVOCALMORPHER_MINWAVEFORM;
+
+constexpr auto EAXVOCALMORPHER_MINRATE = 0.0F;
+constexpr auto EAXVOCALMORPHER_MAXRATE = 10.0F;
+constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F;
+
+
+// Pitch Shifter Effect
+
+extern const GUID EAX_PITCHSHIFTER_EFFECT;
+
+enum EAXPITCHSHIFTER_PROPERTY :
+    unsigned int
+{
+    EAXPITCHSHIFTER_NONE,
+    EAXPITCHSHIFTER_ALLPARAMETERS,
+    EAXPITCHSHIFTER_COARSETUNE,
+    EAXPITCHSHIFTER_FINETUNE,
+}; // EAXPITCHSHIFTER_PROPERTY
+
+struct EAXPITCHSHIFTERPROPERTIES
+{
+    long lCoarseTune; // Amount of pitch shift (semitones)
+    long lFineTune; // Amount of pitch shift (cents)
+}; // EAXPITCHSHIFTERPROPERTIES
+
+
+constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12L;
+constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12L;
+constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12L;
+
+constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50L;
+constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50L;
+constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L;
+
+
+// Ring Modulator Effect
+
+extern const GUID EAX_RINGMODULATOR_EFFECT;
+
+enum EAXRINGMODULATOR_PROPERTY :
+    unsigned int
+{
+    EAXRINGMODULATOR_NONE,
+    EAXRINGMODULATOR_ALLPARAMETERS,
+    EAXRINGMODULATOR_FREQUENCY,
+    EAXRINGMODULATOR_HIGHPASSCUTOFF,
+    EAXRINGMODULATOR_WAVEFORM,
+}; // EAXRINGMODULATOR_PROPERTY
+
+enum :
+    unsigned long
+{
+    EAX_RINGMODULATOR_SINUSOID,
+    EAX_RINGMODULATOR_SAWTOOTH,
+    EAX_RINGMODULATOR_SQUARE,
+};
+
+// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS
+struct EAXRINGMODULATORPROPERTIES
+{
+    float flFrequency; // Frequency of modulation (Hz)
+    float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz)
+    unsigned long ulWaveform; // Waveform selector - see enum above
+}; // EAXRINGMODULATORPROPERTIES
+
+
+constexpr auto EAXRINGMODULATOR_MINFREQUENCY = 0.0F;
+constexpr auto EAXRINGMODULATOR_MAXFREQUENCY = 8000.0F;
+constexpr auto EAXRINGMODULATOR_DEFAULTFREQUENCY = 440.0F;
+
+constexpr auto EAXRINGMODULATOR_MINHIGHPASSCUTOFF = 0.0F;
+constexpr auto EAXRINGMODULATOR_MAXHIGHPASSCUTOFF = 24000.0F;
+constexpr auto EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF = 800.0F;
+
+constexpr auto EAXRINGMODULATOR_MINWAVEFORM = 0UL;
+constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2UL;
+constexpr auto EAXRINGMODULATOR_DEFAULTWAVEFORM = EAXRINGMODULATOR_MINWAVEFORM;
+
+
+using LPEAXSET = ALenum(AL_APIENTRY*)(
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_buffer,
+    ALuint property_size);
+
+using LPEAXGET = ALenum(AL_APIENTRY*)(
+    const GUID* property_set_id,
+    ALuint property_id,
+    ALuint property_source_id,
+    ALvoid* property_buffer,
+    ALuint property_size);
+
+
+#endif // !EAX_API_INCLUDED

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

@@ -0,0 +1,324 @@
+#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
+    };
+}

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

@@ -0,0 +1,117 @@
+#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

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

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

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

@@ -0,0 +1,44 @@
+#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

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

@@ -0,0 +1,63 @@
+#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;
+}

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

@@ -0,0 +1,25 @@
+#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

+ 71 - 0
libs/openal-soft/al/eax_fx_slot_index.cpp

@@ -0,0 +1,71 @@
+#include "config.h"
+
+#include "eax_fx_slot_index.h"
+
+#include "eax_exception.h"
+
+
+namespace
+{
+
+
+class EaxFxSlotIndexException :
+    public EaxException
+{
+public:
+    explicit EaxFxSlotIndexException(
+        const char* message)
+        :
+        EaxException{"EAX_FX_SLOT_INDEX", message}
+    {
+    }
+}; // EaxFxSlotIndexException
+
+
+} // namespace
+
+
+void EaxFxSlotIndex::set(EaxFxSlotIndexValue index)
+{
+    if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS})
+        fail("Index out of range.");
+
+    emplace(index);
+}
+
+void EaxFxSlotIndex::set(const GUID &guid)
+{
+    if (false)
+    {
+    }
+    else if (guid == EAX_NULL_GUID)
+    {
+        reset();
+    }
+    else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0)
+    {
+        emplace(0u);
+    }
+    else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1)
+    {
+        emplace(1u);
+    }
+    else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2)
+    {
+        emplace(2u);
+    }
+    else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3)
+    {
+        emplace(3u);
+    }
+    else
+    {
+        fail("Unsupported GUID.");
+    }
+}
+
+[[noreturn]]
+void EaxFxSlotIndex::fail(const char* message)
+{
+    throw EaxFxSlotIndexException{message};
+}

+ 41 - 0
libs/openal-soft/al/eax_fx_slot_index.h

@@ -0,0 +1,41 @@
+#ifndef EAX_FX_SLOT_INDEX_INCLUDED
+#define EAX_FX_SLOT_INDEX_INCLUDED
+
+
+#include <cstddef>
+
+#include "aloptional.h"
+#include "eax_api.h"
+
+
+using EaxFxSlotIndexValue = std::size_t;
+
+class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
+{
+public:
+    using al::optional<EaxFxSlotIndexValue>::optional;
+
+    EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
+    EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }
+
+    void set(EaxFxSlotIndexValue index);
+    void set(const GUID& guid);
+
+private:
+    [[noreturn]]
+    static void fail(const char *message);
+}; // EaxFxSlotIndex
+
+inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
+{
+    if(lhs.has_value() != rhs.has_value())
+        return false;
+    if(lhs.has_value())
+        return *lhs == *rhs;
+    return true;
+}
+
+inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
+{ return !(lhs == rhs); }
+
+#endif // !EAX_FX_SLOT_INDEX_INCLUDED

+ 84 - 0
libs/openal-soft/al/eax_fx_slots.cpp

@@ -0,0 +1,84 @@
+#include "config.h"
+
+#include "eax_fx_slots.h"
+
+#include <array>
+
+#include "eax_exception.h"
+
+#include "eax_api.h"
+
+
+namespace
+{
+
+
+class EaxFxSlotsException :
+    public EaxException
+{
+public:
+    explicit EaxFxSlotsException(
+        const char* message)
+        :
+        EaxException{"EAX_FX_SLOTS", message}
+    {
+    }
+}; // EaxFxSlotsException
+
+
+} // namespace
+
+
+void EaxFxSlots::initialize(
+    ALCcontext& al_context)
+{
+    initialize_fx_slots(al_context);
+}
+
+void EaxFxSlots::uninitialize() noexcept
+{
+    for (auto& fx_slot : fx_slots_)
+    {
+        fx_slot = nullptr;
+    }
+}
+
+const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const
+{
+    if(!index.has_value())
+        fail("Empty index.");
+    return *fx_slots_[index.value()];
+}
+
+ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index)
+{
+    if(!index.has_value())
+        fail("Empty index.");
+    return *fx_slots_[index.value()];
+}
+
+void EaxFxSlots::unlock_legacy() noexcept
+{
+    fx_slots_[0]->eax_unlock_legacy();
+    fx_slots_[1]->eax_unlock_legacy();
+}
+
+[[noreturn]]
+void EaxFxSlots::fail(
+    const char* message)
+{
+    throw EaxFxSlotsException{message};
+}
+
+void EaxFxSlots::initialize_fx_slots(
+    ALCcontext& al_context)
+{
+    auto fx_slot_index = EaxFxSlotIndexValue{};
+
+    for (auto& fx_slot : fx_slots_)
+    {
+        fx_slot = eax_create_al_effect_slot(al_context);
+        fx_slot->eax_initialize(al_context, fx_slot_index);
+        fx_slot_index += 1;
+    }
+}

+ 54 - 0
libs/openal-soft/al/eax_fx_slots.h

@@ -0,0 +1,54 @@
+#ifndef EAX_FX_SLOTS_INCLUDED
+#define EAX_FX_SLOTS_INCLUDED
+
+
+#include <array>
+
+#include "al/auxeffectslot.h"
+
+#include "eax_api.h"
+
+#include "eax_fx_slot_index.h"
+
+
+class EaxFxSlots
+{
+public:
+    void initialize(
+        ALCcontext& al_context);
+
+    void uninitialize() noexcept;
+
+    void commit()
+    {
+        for(auto& fx_slot : fx_slots_)
+            fx_slot->eax_commit();
+    }
+
+
+    const ALeffectslot& get(
+        EaxFxSlotIndex index) const;
+
+    ALeffectslot& get(
+        EaxFxSlotIndex index);
+
+    void unlock_legacy() noexcept;
+
+
+private:
+    using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
+
+
+    Items fx_slots_{};
+
+
+    [[noreturn]]
+    static void fail(
+        const char* message);
+
+    void initialize_fx_slots(
+        ALCcontext& al_context);
+}; // EaxFxSlots
+
+
+#endif // !EAX_FX_SLOTS_INCLUDED

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

@@ -0,0 +1,21 @@
+#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";

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

@@ -0,0 +1,22 @@
+#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

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

@@ -0,0 +1,36 @@
+#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.");
+    }
+}

+ 132 - 0
libs/openal-soft/al/eax_utils.h

@@ -0,0 +1,132 @@
+#ifndef EAX_UTILS_INCLUDED
+#define EAX_UTILS_INCLUDED
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <type_traits>
+
+
+struct EaxAlLowPassParam
+{
+    float gain;
+    float gain_hf;
+}; // EaxAlLowPassParam
+
+
+void eax_log_exception(
+    const char* message = nullptr) noexcept;
+
+
+template<
+    typename TException,
+    typename TValue
+>
+void eax_validate_range(
+    const char* value_name,
+    const TValue& value,
+    const TValue& min_value,
+    const TValue& max_value)
+{
+    if (value >= min_value && value <= max_value)
+    {
+        return;
+    }
+
+    const auto message =
+        std::string{value_name} +
+        " out of range (value: " +
+        std::to_string(value) + "; min: " +
+        std::to_string(min_value) + "; max: " +
+        std::to_string(max_value) + ").";
+
+    throw TException{message.c_str()};
+}
+
+
+namespace detail
+{
+
+
+template<
+    typename T
+>
+struct EaxIsBitFieldStruct
+{
+private:
+    using yes = std::true_type;
+    using no = std::false_type;
+
+    template<
+        typename U
+    >
+    static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
+
+    template<
+        typename
+    >
+    static no test(...);
+
+
+public:
+    static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
+}; // EaxIsBitFieldStruct
+
+
+template<
+    typename T,
+    typename TValue
+>
+inline bool eax_bit_fields_are_equal(
+    const T& lhs,
+    const T& rhs) noexcept
+{
+    static_assert(sizeof(T) == sizeof(TValue), "Invalid type size.");
+
+    return reinterpret_cast<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
+}
+
+
+} // namespace detail
+
+
+template<
+    typename T,
+    std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
+>
+inline bool operator==(
+    const T& lhs,
+    const T& rhs) noexcept
+{
+    using Value = std::conditional_t<
+        sizeof(T) == 1,
+        std::uint8_t,
+        std::conditional_t<
+            sizeof(T) == 2,
+            std::uint16_t,
+            std::conditional_t<
+                sizeof(T) == 4,
+                std::uint32_t,
+                void
+            >
+        >
+    >;
+
+    static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
+
+    return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
+}
+
+template<
+    typename T,
+    std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
+>
+inline bool operator!=(
+    const T& lhs,
+    const T& rhs) noexcept
+{
+    return !(lhs == rhs);
+}
+
+
+#endif // !EAX_UTILS_INCLUDED

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

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

+ 38 - 0
libs/openal-soft/al/eax_x_ram.h

@@ -0,0 +1,38 @@
+#ifndef EAX_X_RAM_INCLUDED
+#define EAX_X_RAM_INCLUDED
+
+
+#include "AL/al.h"
+
+
+constexpr auto eax_x_ram_min_size = ALsizei{};
+constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024};
+
+
+constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201};
+constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202};
+
+constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203};
+constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204};
+constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205};
+
+
+constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE";
+constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE";
+
+constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
+constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
+constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
+
+
+ALboolean AL_APIENTRY EAXSetBufferMode(
+    ALsizei n,
+    const ALuint* buffers,
+    ALint value);
+
+ALenum AL_APIENTRY EAXGetBufferMode(
+    ALuint buffer,
+    ALint* pReserved);
+
+
+#endif // !EAX_X_RAM_INCLUDED

+ 36 - 17
libs/openal-soft/al/effect.cpp

@@ -39,17 +39,23 @@
 #include "AL/efx.h"
 
 #include "albit.h"
-#include "alcmain.h"
-#include "alcontext.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 "alstring.h"
 #include "core/except.h"
 #include "core/logging.h"
-#include "effects/base.h"
 #include "opthelpers.h"
 #include "vector.h"
 
+#ifdef ALSOFT_EAX
+#include <cassert>
+
+#include "eax_exception.h"
+#endif // ALSOFT_EAX
 
 const EffectList gEffectList[16]{
     { "eaxreverb",   EAXREVERB_EFFECT,   AL_EFFECT_EAXREVERB },
@@ -181,12 +187,12 @@ ALeffect *AllocEffect(ALCdevice *device)
 {
     auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
         [](const EffectSubList &entry) noexcept -> bool
-        { return entry.FreeMask != 0; }
-    );
+        { return entry.FreeMask != 0; });
     auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
     auto slidx = static_cast<ALuint>(al::countr_zero(sublist->FreeMask));
+    ASSUME(slidx < 64);
 
-    ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}};
+    ALeffect *effect{al::construct_at(sublist->Effects + slidx)};
     InitEffectParams(effect, AL_EFFECT_NULL);
 
     /* Add 1 to avoid effect ID 0. */
@@ -233,7 +239,7 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
     if UNLIKELY(n <= 0) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
     if(!EnsureEffects(device, static_cast<ALuint>(n)))
     {
@@ -273,7 +279,7 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
     if UNLIKELY(n <= 0) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     /* First try to find any effects that are invalid. */
@@ -304,7 +310,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if LIKELY(context)
     {
-        ALCdevice *device{context->mDevice.get()};
+        ALCdevice *device{context->mALDevice.get()};
         std::lock_guard<std::mutex> _{device->EffectLock};
         if(!effect || LookupEffect(device, effect))
             return AL_TRUE;
@@ -319,7 +325,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
@@ -369,7 +375,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
@@ -392,7 +398,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
@@ -415,7 +421,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     ALeffect *aleffect{LookupEffect(device, effect)};
@@ -438,7 +444,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
@@ -470,7 +476,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
@@ -493,7 +499,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
@@ -516,7 +522,7 @@ START_API_FUNC
     ContextRef context{GetContextRef()};
     if UNLIKELY(!context) return;
 
-    ALCdevice *device{context->mDevice.get()};
+    ALCdevice *device{context->mALDevice.get()};
     std::lock_guard<std::mutex> _{device->EffectLock};
 
     const ALeffect *aleffect{LookupEffect(device, effect)};
@@ -744,3 +750,16 @@ void LoadReverbPreset(const char *name, ALeffect *effect)
 
     WARN("Reverb preset '%s' not found\n", name);
 }
+
+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;
+}

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

@@ -57,4 +57,6 @@ void InitEffect(ALeffect *effect);
 
 void LoadReverbPreset(const char *name, ALeffect *effect);
 
+bool IsValidEffectType(ALenum type) noexcept;
+
 #endif

+ 440 - 1
libs/openal-soft/al/effects/autowah.cpp

@@ -8,9 +8,17 @@
 
 #include "AL/efx.h"
 
-#include "effects/base.h"
+#include "alc/effects/base.h"
 #include "effects.h"
 
+#ifdef ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
+
 namespace {
 
 void Autowah_setParamf(EffectProps *props, ALenum param, float val)
@@ -107,3 +115,434 @@ EffectProps genDefaultProps() noexcept
 DEFINE_ALEFFECT_VTABLE(Autowah);
 
 const EffectProps AutowahEffectProps{genDefaultProps()};
+
+#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();
+
+
+    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}
+    {
+    }
+}; // 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();
+}
+
+void EaxAutoWahEffect::get(const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_id())
+    {
+        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."};
+    }
+}
+
+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);
+}
+
+void EaxAutoWahEffect::defer_peak_level(
+    long lPeakLevel)
+{
+    eax_d_.lPeakLevel = lPeakLevel;
+    eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel);
+}
+
+void EaxAutoWahEffect::defer_all(
+    const EAXAUTOWAHPROPERTIES& eax_all)
+{
+    validate_all(eax_all);
+
+    defer_attack_time(eax_all.flAttackTime);
+    defer_release_time(eax_all.flReleaseTime);
+    defer_resonance(eax_all.lResonance);
+    defer_peak_level(eax_all.lPeakLevel);
+}
+
+void EaxAutoWahEffect::defer_attack_time(
+    const EaxEaxCall& eax_call)
+{
+    const auto& attack_time =
+        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flAttackTime)>();
+
+    validate_attack_time(attack_time);
+    defer_attack_time(attack_time);
+}
+
+void EaxAutoWahEffect::defer_release_time(
+    const EaxEaxCall& eax_call)
+{
+    const auto& release_time =
+        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flReleaseTime)>();
+
+    validate_release_time(release_time);
+    defer_release_time(release_time);
+}
+
+void EaxAutoWahEffect::defer_resonance(
+    const EaxEaxCall& eax_call)
+{
+    const auto& resonance =
+        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lResonance)>();
+
+    validate_resonance(resonance);
+    defer_resonance(resonance);
+}
+
+void EaxAutoWahEffect::defer_peak_level(
+    const EaxEaxCall& eax_call)
+{
+    const auto& peak_level =
+        eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lPeakLevel)>();
+
+    validate_peak_level(peak_level);
+    defer_peak_level(peak_level);
+}
+
+void EaxAutoWahEffect::defer_all(
+    const EaxEaxCall& eax_call)
+{
+    const auto& all =
+        eax_call.get_value<EaxAutoWahEffectException, const EAXAUTOWAHPROPERTIES>();
+
+    validate_all(all);
+    defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxAutoWahEffect::apply_deferred()
+{
+    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)
+    {
+        set_efx_peak_gain();
+    }
+
+    eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{};
+
+    return true;
+}
+
+void EaxAutoWahEffect::set(const EaxEaxCall& eax_call)
+{
+    switch (eax_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."};
+    }
+}
+
+} // namespace
+
+EaxEffectUPtr eax_create_eax_auto_wah_effect()
+{
+    return std::make_unique<::EaxAutoWahEffect>();
+}
+
+#endif // ALSOFT_EAX

+ 1082 - 1
libs/openal-soft/al/effects/chorus.cpp

@@ -1,17 +1,31 @@
 
 #include "config.h"
 
+#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"
-#include "effects/base.h"
+
+#ifdef ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
 
 
 namespace {
 
+static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small");
+static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small");
+
 static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
 static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
 
@@ -274,3 +288,1070 @@ const EffectProps ChorusEffectProps{genDefaultChorusProps()};
 DEFINE_ALEFFECT_VTABLE(Flanger);
 
 const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
+
+
+#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{})
+    {
+        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_ = EaxChorusEffectDirtyFlags{};
+
+    return true;
+}
+
+void EaxChorusEffect::set(const EaxEaxCall& eax_call)
+{
+    switch(eax_call.get_property_id())
+    {
+        case EAXCHORUS_NONE:
+            break;
+
+        case EAXCHORUS_ALLPARAMETERS:
+            defer_all(eax_call);
+            break;
+
+        case EAXCHORUS_WAVEFORM:
+            defer_waveform(eax_call);
+            break;
+
+        case EAXCHORUS_PHASE:
+            defer_phase(eax_call);
+            break;
+
+        case EAXCHORUS_RATE:
+            defer_rate(eax_call);
+            break;
+
+        case EAXCHORUS_DEPTH:
+            defer_depth(eax_call);
+            break;
+
+        case EAXCHORUS_FEEDBACK:
+            defer_feedback(eax_call);
+            break;
+
+        case EAXCHORUS_DELAY:
+            defer_delay(eax_call);
+            break;
+
+        default:
+            throw EaxChorusEffectException{"Unsupported 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}
+    {
+    }
+}; // 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:
+            break;
+
+        case EAXFLANGER_ALLPARAMETERS:
+            eax_call.set_value<EaxFlangerEffectException>(eax_);
+            break;
+
+        case EAXFLANGER_WAVEFORM:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.ulWaveform);
+            break;
+
+        case EAXFLANGER_PHASE:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.lPhase);
+            break;
+
+        case EAXFLANGER_RATE:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.flRate);
+            break;
+
+        case EAXFLANGER_DEPTH:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.flDepth);
+            break;
+
+        case EAXFLANGER_FEEDBACK:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.flFeedback);
+            break;
+
+        case EAXFLANGER_DELAY:
+            eax_call.set_value<EaxFlangerEffectException>(eax_.flDelay);
+            break;
+
+        default:
+            throw EaxFlangerEffectException{"Unsupported 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);
+}
+
+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);
+}
+
+void EaxFlangerEffect::defer_waveform(
+    unsigned long ulWaveform)
+{
+    eax_d_.ulWaveform = ulWaveform;
+    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxFlangerEffect::defer_phase(
+    long lPhase)
+{
+    eax_d_.lPhase = lPhase;
+    eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase);
+}
+
+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);
+}
+
+void EaxFlangerEffect::defer_feedback(
+    float flFeedback)
+{
+    eax_d_.flFeedback = flFeedback;
+    eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback);
+}
+
+void EaxFlangerEffect::defer_delay(
+    float flDelay)
+{
+    eax_d_.flDelay = flDelay;
+    eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay);
+}
+
+void EaxFlangerEffect::defer_all(
+    const EAXFLANGERPROPERTIES& all)
+{
+    defer_waveform(all.ulWaveform);
+    defer_phase(all.lPhase);
+    defer_rate(all.flRate);
+    defer_depth(all.flDepth);
+    defer_feedback(all.flDelay);
+    defer_delay(all.flDelay);
+}
+
+void EaxFlangerEffect::defer_waveform(
+    const EaxEaxCall& eax_call)
+{
+    const auto& waveform =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::ulWaveform)>();
+
+    validate_waveform(waveform);
+    defer_waveform(waveform);
+}
+
+void EaxFlangerEffect::defer_phase(
+    const EaxEaxCall& eax_call)
+{
+    const auto& phase =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::lPhase)>();
+
+    validate_phase(phase);
+    defer_phase(phase);
+}
+
+void EaxFlangerEffect::defer_rate(
+    const EaxEaxCall& eax_call)
+{
+    const auto& rate =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flRate)>();
+
+    validate_rate(rate);
+    defer_rate(rate);
+}
+
+void EaxFlangerEffect::defer_depth(
+    const EaxEaxCall& eax_call)
+{
+    const auto& depth =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDepth)>();
+
+    validate_depth(depth);
+    defer_depth(depth);
+}
+
+void EaxFlangerEffect::defer_feedback(
+    const EaxEaxCall& eax_call)
+{
+    const auto& feedback =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flFeedback)>();
+
+    validate_feedback(feedback);
+    defer_feedback(feedback);
+}
+
+void EaxFlangerEffect::defer_delay(
+    const EaxEaxCall& eax_call)
+{
+    const auto& delay =
+        eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDelay)>();
+
+    validate_delay(delay);
+    defer_delay(delay);
+}
+
+void EaxFlangerEffect::defer_all(
+    const EaxEaxCall& eax_call)
+{
+    const auto& all =
+        eax_call.get_value<EaxFlangerEffectException, const EAXFLANGERPROPERTIES>();
+
+    validate_all(all);
+    defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxFlangerEffect::apply_deferred()
+{
+    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;
+}
+
+void EaxFlangerEffect::set(const EaxEaxCall& eax_call)
+{
+    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."};
+    }
+}
+
+} // namespace
+
+EaxEffectUPtr eax_create_eax_flanger_effect()
+{
+    return std::make_unique<EaxFlangerEffect>();
+}
+
+#endif // ALSOFT_EAX

+ 225 - 1
libs/openal-soft/al/effects/compressor.cpp

@@ -4,8 +4,15 @@
 #include "AL/al.h"
 #include "AL/efx.h"
 
+#include "alc/effects/base.h"
 #include "effects.h"
-#include "effects/base.h"
+
+#ifdef ALSOFT_EAX
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
 
 
 namespace {
@@ -70,3 +77,220 @@ EffectProps genDefaultProps() noexcept
 DEFINE_ALEFFECT_VTABLE(Compressor);
 
 const EffectProps CompressorEffectProps{genDefaultProps()};
+
+#ifdef ALSOFT_EAX
+namespace {
+
+using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t;
+
+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}
+    {
+    }
+}; // EaxCompressorEffectException
+
+
+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())
+    {
+        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."};
+    }
+}
+
+void EaxCompressorEffect::validate_on_off(
+    unsigned long ulOnOff)
+{
+    eax_validate_range<EaxCompressorEffectException>(
+        "On-Off",
+        ulOnOff,
+        EAXAGCCOMPRESSOR_MINONOFF,
+        EAXAGCCOMPRESSOR_MAXONOFF);
+}
+
+void EaxCompressorEffect::validate_all(
+    const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+{
+    validate_on_off(eax_all.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_on_off(
+    unsigned long ulOnOff)
+{
+    eax_d_.ulOnOff = ulOnOff;
+    eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_all(
+    const EAXAGCCOMPRESSORPROPERTIES& eax_all)
+{
+    defer_on_off(eax_all.ulOnOff);
+}
+
+void EaxCompressorEffect::defer_on_off(
+    const EaxEaxCall& eax_call)
+{
+    const auto& on_off =
+        eax_call.get_value<EaxCompressorEffectException, const decltype(EAXAGCCOMPRESSORPROPERTIES::ulOnOff)>();
+
+    validate_on_off(on_off);
+    defer_on_off(on_off);
+}
+
+void EaxCompressorEffect::defer_all(
+    const EaxEaxCall& eax_call)
+{
+    const auto& all =
+        eax_call.get_value<EaxCompressorEffectException, const EAXAGCCOMPRESSORPROPERTIES>();
+
+    validate_all(all);
+    defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxCompressorEffect::apply_deferred()
+{
+    if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{})
+    {
+        return false;
+    }
+
+    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)
+{
+    switch(eax_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."};
+    }
+}
+
+} // namespace
+
+EaxEffectUPtr eax_create_eax_compressor_effect()
+{
+    return std::make_unique<EaxCompressorEffect>();
+}
+
+#endif // ALSOFT_EAX

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