Browse Source

update openal-soft
sync point: master-ac5d40e40a0155351fe1be4aab30017b6a13a859

AzaezelX 4 years ago
parent
commit
3603188b7f
100 changed files with 23141 additions and 25850 deletions
  1. 8 4
      Engine/lib/openal-soft/.gitignore
  2. 73 15
      Engine/lib/openal-soft/.travis.yml
  3. 0 4720
      Engine/lib/openal-soft/Alc/ALc.c
  4. 0 1927
      Engine/lib/openal-soft/Alc/ALu.c
  5. 4136 0
      Engine/lib/openal-soft/Alc/alc.cpp
  6. 371 0
      Engine/lib/openal-soft/Alc/alcmain.h
  7. 0 675
      Engine/lib/openal-soft/Alc/alconfig.c
  8. 542 0
      Engine/lib/openal-soft/Alc/alconfig.cpp
  9. 10 7
      Engine/lib/openal-soft/Alc/alconfig.h
  10. 282 0
      Engine/lib/openal-soft/Alc/alcontext.h
  11. 0 58
      Engine/lib/openal-soft/Alc/alstring.h
  12. 2018 0
      Engine/lib/openal-soft/Alc/alu.cpp
  13. 141 0
      Engine/lib/openal-soft/Alc/alu.h
  14. 0 566
      Engine/lib/openal-soft/Alc/ambdec.c
  15. 0 46
      Engine/lib/openal-soft/Alc/ambdec.h
  16. 49 0
      Engine/lib/openal-soft/Alc/async_event.h
  17. 0 1456
      Engine/lib/openal-soft/Alc/backends/alsa.c
  18. 1268 0
      Engine/lib/openal-soft/Alc/backends/alsa.cpp
  19. 19 0
      Engine/lib/openal-soft/Alc/backends/alsa.h
  20. 0 83
      Engine/lib/openal-soft/Alc/backends/base.c
  21. 195 0
      Engine/lib/openal-soft/Alc/backends/base.cpp
  22. 88 137
      Engine/lib/openal-soft/Alc/backends/base.h
  23. 0 810
      Engine/lib/openal-soft/Alc/backends/coreaudio.c
  24. 686 0
      Engine/lib/openal-soft/Alc/backends/coreaudio.cpp
  25. 19 0
      Engine/lib/openal-soft/Alc/backends/coreaudio.h
  26. 0 1078
      Engine/lib/openal-soft/Alc/backends/dsound.c
  27. 868 0
      Engine/lib/openal-soft/Alc/backends/dsound.cpp
  28. 19 0
      Engine/lib/openal-soft/Alc/backends/dsound.h
  29. 0 607
      Engine/lib/openal-soft/Alc/backends/jack.c
  30. 601 0
      Engine/lib/openal-soft/Alc/backends/jack.cpp
  31. 19 0
      Engine/lib/openal-soft/Alc/backends/jack.h
  32. 0 128
      Engine/lib/openal-soft/Alc/backends/loopback.c
  33. 79 0
      Engine/lib/openal-soft/Alc/backends/loopback.cpp
  34. 19 0
      Engine/lib/openal-soft/Alc/backends/loopback.h
  35. 0 221
      Engine/lib/openal-soft/Alc/backends/null.c
  36. 179 0
      Engine/lib/openal-soft/Alc/backends/null.cpp
  37. 19 0
      Engine/lib/openal-soft/Alc/backends/null.h
  38. 250 0
      Engine/lib/openal-soft/Alc/backends/oboe.cpp
  39. 19 0
      Engine/lib/openal-soft/Alc/backends/oboe.h
  40. 0 1074
      Engine/lib/openal-soft/Alc/backends/opensl.c
  41. 975 0
      Engine/lib/openal-soft/Alc/backends/opensl.cpp
  42. 19 0
      Engine/lib/openal-soft/Alc/backends/opensl.h
  43. 0 878
      Engine/lib/openal-soft/Alc/backends/oss.c
  44. 686 0
      Engine/lib/openal-soft/Alc/backends/oss.cpp
  45. 19 0
      Engine/lib/openal-soft/Alc/backends/oss.h
  46. 0 558
      Engine/lib/openal-soft/Alc/backends/portaudio.c
  47. 442 0
      Engine/lib/openal-soft/Alc/backends/portaudio.cpp
  48. 19 0
      Engine/lib/openal-soft/Alc/backends/portaudio.h
  49. 0 1919
      Engine/lib/openal-soft/Alc/backends/pulseaudio.c
  50. 1521 0
      Engine/lib/openal-soft/Alc/backends/pulseaudio.cpp
  51. 19 0
      Engine/lib/openal-soft/Alc/backends/pulseaudio.h
  52. 0 1073
      Engine/lib/openal-soft/Alc/backends/qsa.c
  53. 0 287
      Engine/lib/openal-soft/Alc/backends/sdl2.c
  54. 216 0
      Engine/lib/openal-soft/Alc/backends/sdl2.cpp
  55. 19 0
      Engine/lib/openal-soft/Alc/backends/sdl2.h
  56. 0 342
      Engine/lib/openal-soft/Alc/backends/sndio.c
  57. 517 0
      Engine/lib/openal-soft/Alc/backends/sndio.cpp
  58. 19 0
      Engine/lib/openal-soft/Alc/backends/sndio.h
  59. 0 360
      Engine/lib/openal-soft/Alc/backends/solaris.c
  60. 294 0
      Engine/lib/openal-soft/Alc/backends/solaris.cpp
  61. 19 0
      Engine/lib/openal-soft/Alc/backends/solaris.h
  62. 0 2044
      Engine/lib/openal-soft/Alc/backends/wasapi.c
  63. 1848 0
      Engine/lib/openal-soft/Alc/backends/wasapi.cpp
  64. 19 0
      Engine/lib/openal-soft/Alc/backends/wasapi.h
  65. 0 453
      Engine/lib/openal-soft/Alc/backends/wave.c
  66. 406 0
      Engine/lib/openal-soft/Alc/backends/wave.cpp
  67. 19 0
      Engine/lib/openal-soft/Alc/backends/wave.h
  68. 0 792
      Engine/lib/openal-soft/Alc/backends/winmm.c
  69. 631 0
      Engine/lib/openal-soft/Alc/backends/winmm.cpp
  70. 19 0
      Engine/lib/openal-soft/Alc/backends/winmm.h
  71. 0 492
      Engine/lib/openal-soft/Alc/bformatdec.c
  72. 298 0
      Engine/lib/openal-soft/Alc/bformatdec.cpp
  73. 56 38
      Engine/lib/openal-soft/Alc/bformatdec.h
  74. 38 0
      Engine/lib/openal-soft/Alc/buffer_storage.cpp
  75. 72 0
      Engine/lib/openal-soft/Alc/buffer_storage.h
  76. 3 59
      Engine/lib/openal-soft/Alc/compat.h
  77. 0 468
      Engine/lib/openal-soft/Alc/converter.c
  78. 371 0
      Engine/lib/openal-soft/Alc/converter.cpp
  79. 41 37
      Engine/lib/openal-soft/Alc/converter.h
  80. 0 15
      Engine/lib/openal-soft/Alc/cpu_caps.h
  81. 212 0
      Engine/lib/openal-soft/Alc/effects/autowah.cpp
  82. 212 0
      Engine/lib/openal-soft/Alc/effects/base.h
  83. 0 555
      Engine/lib/openal-soft/Alc/effects/chorus.c
  84. 287 0
      Engine/lib/openal-soft/Alc/effects/chorus.cpp
  85. 0 250
      Engine/lib/openal-soft/Alc/effects/compressor.c
  86. 168 0
      Engine/lib/openal-soft/Alc/effects/compressor.cpp
  87. 567 0
      Engine/lib/openal-soft/Alc/effects/convolution.cpp
  88. 0 184
      Engine/lib/openal-soft/Alc/effects/dedicated.c
  89. 110 0
      Engine/lib/openal-soft/Alc/effects/dedicated.cpp
  90. 0 287
      Engine/lib/openal-soft/Alc/effects/distortion.c
  91. 167 0
      Engine/lib/openal-soft/Alc/effects/distortion.cpp
  92. 0 310
      Engine/lib/openal-soft/Alc/effects/echo.c
  93. 168 0
      Engine/lib/openal-soft/Alc/effects/echo.cpp
  94. 0 355
      Engine/lib/openal-soft/Alc/effects/equalizer.c
  95. 184 0
      Engine/lib/openal-soft/Alc/effects/equalizer.cpp
  96. 232 0
      Engine/lib/openal-soft/Alc/effects/fshifter.cpp
  97. 0 303
      Engine/lib/openal-soft/Alc/effects/modulator.c
  98. 173 0
      Engine/lib/openal-soft/Alc/effects/modulator.cpp
  99. 0 179
      Engine/lib/openal-soft/Alc/effects/null.c
  100. 79 0
      Engine/lib/openal-soft/Alc/effects/null.cpp

+ 8 - 4
Engine/lib/openal-soft/.gitignore

@@ -1,5 +1,9 @@
 build*/
-winbuild/
-win64build/
-openal-soft.kdev4
-.kdev4/
+winbuild
+win64build
+
+## kdevelop
+*.kdev4
+
+## qt-creator
+CMakeLists.txt.user*

+ 73 - 15
Engine/lib/openal-soft/.travis.yml

@@ -1,13 +1,19 @@
-language: c
+language: cpp
 matrix:
   include:
     - os: linux
-      dist: trusty
+      dist: xenial
     - os: linux
       dist: trusty
       env:
         - BUILD_ANDROID=true
+    - os: freebsd
+      compiler: clang
+    - os: osx
     - os: osx
+      osx_image: xcode11
+      env:
+        - BUILD_IOS=true
 sudo: required
 install:
   - >
@@ -24,19 +30,44 @@ install:
     fi
   - >
     if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
-      curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r15-linux-x86_64.zip
+      curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip
       unzip -q ~/android-ndk.zip -d ~ \
-        'android-ndk-r15/build/cmake/*' \
-        'android-ndk-r15/build/core/toolchains/arm-linux-androideabi-*/*' \
-        'android-ndk-r15/platforms/android-14/arch-arm/*' \
-        'android-ndk-r15/source.properties' \
-        'android-ndk-r15/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/*' \
-        'android-ndk-r15/sources/cxx-stl/gnu-libstdc++/4.9/include/*' \
-        'android-ndk-r15/sysroot/*' \
-        'android-ndk-r15/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
-        'android-ndk-r15/toolchains/llvm/prebuilt/linux-x86_64/*'
+        'android-ndk-r21/build/cmake/*' \
+        'android-ndk-r21/build/core/toolchains/arm-linux-androideabi-*/*' \
+        'android-ndk-r21/platforms/android-16/arch-arm/*' \
+        'android-ndk-r21/source.properties' \
+        'android-ndk-r21/sources/android/support/include/*' \
+        'android-ndk-r21/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
+        'android-ndk-r21/sources/cxx-stl/llvm-libc++/include/*' \
+        'android-ndk-r21/sysroot/*' \
+        'android-ndk-r21/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
+        'android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/*'
+      export OBOE_LOC=~/oboe
+      git clone --depth 1 -b 1.3-stable https://github.com/google/oboe "$OBOE_LOC"
+    fi
+  - >
+    if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
+      # Install Ninja as it's used downstream.
+      # Install dependencies for all supported backends.
+      # Install Qt5 dependency for alsoft-config.
+      # Install ffmpeg for examples.
+      sudo pkg install -y \
+        alsa-lib \
+        ffmpeg \
+        jackit \
+        libmysofa \
+        ninja \
+        portaudio \
+        pulseaudio \
+        qt5-buildtools \
+        qt5-qmake \
+        qt5-widgets \
+        sdl2 \
+        sndio \
+        $NULL
     fi
 script:
+  - cmake --version
   - >
     if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
       cmake \
@@ -51,16 +82,43 @@ script:
   - >
     if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
       cmake \
-        -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r15/build/cmake/android.toolchain.cmake \
+        -DANDROID_STL=c++_shared \
+        -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21/build/cmake/android.toolchain.cmake \
+        -DOBOE_SOURCE="$OBOE_LOC" \
+        -DALSOFT_REQUIRE_OBOE=ON \
         -DALSOFT_REQUIRE_OPENSL=ON \
         -DALSOFT_EMBED_HRTF_DATA=YES \
         .
     fi
   - >
-    if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+    if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
+      cmake -GNinja \
+        -DALSOFT_REQUIRE_ALSA=ON \
+        -DALSOFT_REQUIRE_JACK=ON \
+        -DALSOFT_REQUIRE_OSS=ON \
+        -DALSOFT_REQUIRE_PORTAUDIO=ON \
+        -DALSOFT_REQUIRE_PULSEAUDIO=ON \
+        -DALSOFT_REQUIRE_SDL2=ON \
+        -DALSOFT_REQUIRE_SNDIO=ON \
+        -DALSOFT_EMBED_HRTF_DATA=YES \
+        .
+    fi
+  - >
+    if [[ "${TRAVIS_OS_NAME}" == "osx" && -z "${BUILD_IOS}" ]]; then
+      cmake \
+        -DALSOFT_REQUIRE_COREAUDIO=ON \
+        -DALSOFT_EMBED_HRTF_DATA=YES \
+        .
+    fi
+  - >
+    if [[ "${TRAVIS_OS_NAME}" == "osx" && "${BUILD_IOS}" == "true" ]]; then
       cmake \
+        -GXcode \
+        -DCMAKE_SYSTEM_NAME=iOS \
+        -DALSOFT_OSX_FRAMEWORK=ON \
         -DALSOFT_REQUIRE_COREAUDIO=ON \
         -DALSOFT_EMBED_HRTF_DATA=YES \
+        "-DCMAKE_OSX_ARCHITECTURES=armv7;arm64" \
         .
     fi
-  - make -j2
+  - cmake --build . --clean-first

+ 0 - 4720
Engine/lib/openal-soft/Alc/ALc.c

@@ -1,4720 +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 "version.h"
-
-#include <math.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-#include <ctype.h>
-#include <signal.h>
-
-#include "alMain.h"
-#include "alSource.h"
-#include "alListener.h"
-#include "alSource.h"
-#include "alBuffer.h"
-#include "alFilter.h"
-#include "alEffect.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "mastering.h"
-#include "bformatdec.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-
-#include "fpu_modes.h"
-#include "cpu_caps.h"
-#include "compat.h"
-#include "threads.h"
-#include "alstring.h"
-#include "almalloc.h"
-
-#include "backends/base.h"
-
-
-/************************************************
- * Backends
- ************************************************/
-struct BackendInfo {
-    const char *name;
-    ALCbackendFactory* (*getFactory)(void);
-};
-
-static struct BackendInfo BackendList[] = {
-#ifdef HAVE_JACK
-    { "jack", ALCjackBackendFactory_getFactory },
-#endif
-#ifdef HAVE_PULSEAUDIO
-    { "pulse", ALCpulseBackendFactory_getFactory },
-#endif
-#ifdef HAVE_ALSA
-    { "alsa", ALCalsaBackendFactory_getFactory },
-#endif
-#ifdef HAVE_COREAUDIO
-    { "core", ALCcoreAudioBackendFactory_getFactory },
-#endif
-#ifdef HAVE_OSS
-    { "oss", ALCossBackendFactory_getFactory },
-#endif
-#ifdef HAVE_SOLARIS
-    { "solaris", ALCsolarisBackendFactory_getFactory },
-#endif
-#ifdef HAVE_SNDIO
-    { "sndio", ALCsndioBackendFactory_getFactory },
-#endif
-#ifdef HAVE_QSA
-    { "qsa", ALCqsaBackendFactory_getFactory },
-#endif
-#ifdef HAVE_WASAPI
-    { "wasapi", ALCwasapiBackendFactory_getFactory },
-#endif
-#ifdef HAVE_DSOUND
-    { "dsound", ALCdsoundBackendFactory_getFactory },
-#endif
-#ifdef HAVE_WINMM
-    { "winmm", ALCwinmmBackendFactory_getFactory },
-#endif
-#ifdef HAVE_PORTAUDIO
-    { "port", ALCportBackendFactory_getFactory },
-#endif
-#ifdef HAVE_OPENSL
-    { "opensl", ALCopenslBackendFactory_getFactory },
-#endif
-#ifdef HAVE_SDL2
-    { "sdl2", ALCsdl2BackendFactory_getFactory },
-#endif
-
-    { "null", ALCnullBackendFactory_getFactory },
-#ifdef HAVE_WAVE
-    { "wave", ALCwaveBackendFactory_getFactory },
-#endif
-};
-static ALsizei BackendListSize = COUNTOF(BackendList);
-#undef EmptyFuncs
-
-static struct BackendInfo PlaybackBackend;
-static struct BackendInfo CaptureBackend;
-
-
-/************************************************
- * Functions, enums, and errors
- ************************************************/
-#define DECL(x) { #x, (ALCvoid*)(x) }
-static const struct {
-    const ALCchar *funcName;
-    ALCvoid *address;
-} alcFunctions[] = {
-    DECL(alcCreateContext),
-    DECL(alcMakeContextCurrent),
-    DECL(alcProcessContext),
-    DECL(alcSuspendContext),
-    DECL(alcDestroyContext),
-    DECL(alcGetCurrentContext),
-    DECL(alcGetContextsDevice),
-    DECL(alcOpenDevice),
-    DECL(alcCloseDevice),
-    DECL(alcGetError),
-    DECL(alcIsExtensionPresent),
-    DECL(alcGetProcAddress),
-    DECL(alcGetEnumValue),
-    DECL(alcGetString),
-    DECL(alcGetIntegerv),
-    DECL(alcCaptureOpenDevice),
-    DECL(alcCaptureCloseDevice),
-    DECL(alcCaptureStart),
-    DECL(alcCaptureStop),
-    DECL(alcCaptureSamples),
-
-    DECL(alcSetThreadContext),
-    DECL(alcGetThreadContext),
-
-    DECL(alcLoopbackOpenDeviceSOFT),
-    DECL(alcIsRenderFormatSupportedSOFT),
-    DECL(alcRenderSamplesSOFT),
-
-    DECL(alcDevicePauseSOFT),
-    DECL(alcDeviceResumeSOFT),
-
-    DECL(alcGetStringiSOFT),
-    DECL(alcResetDeviceSOFT),
-
-    DECL(alcGetInteger64vSOFT),
-
-    DECL(alEnable),
-    DECL(alDisable),
-    DECL(alIsEnabled),
-    DECL(alGetString),
-    DECL(alGetBooleanv),
-    DECL(alGetIntegerv),
-    DECL(alGetFloatv),
-    DECL(alGetDoublev),
-    DECL(alGetBoolean),
-    DECL(alGetInteger),
-    DECL(alGetFloat),
-    DECL(alGetDouble),
-    DECL(alGetError),
-    DECL(alIsExtensionPresent),
-    DECL(alGetProcAddress),
-    DECL(alGetEnumValue),
-    DECL(alListenerf),
-    DECL(alListener3f),
-    DECL(alListenerfv),
-    DECL(alListeneri),
-    DECL(alListener3i),
-    DECL(alListeneriv),
-    DECL(alGetListenerf),
-    DECL(alGetListener3f),
-    DECL(alGetListenerfv),
-    DECL(alGetListeneri),
-    DECL(alGetListener3i),
-    DECL(alGetListeneriv),
-    DECL(alGenSources),
-    DECL(alDeleteSources),
-    DECL(alIsSource),
-    DECL(alSourcef),
-    DECL(alSource3f),
-    DECL(alSourcefv),
-    DECL(alSourcei),
-    DECL(alSource3i),
-    DECL(alSourceiv),
-    DECL(alGetSourcef),
-    DECL(alGetSource3f),
-    DECL(alGetSourcefv),
-    DECL(alGetSourcei),
-    DECL(alGetSource3i),
-    DECL(alGetSourceiv),
-    DECL(alSourcePlayv),
-    DECL(alSourceStopv),
-    DECL(alSourceRewindv),
-    DECL(alSourcePausev),
-    DECL(alSourcePlay),
-    DECL(alSourceStop),
-    DECL(alSourceRewind),
-    DECL(alSourcePause),
-    DECL(alSourceQueueBuffers),
-    DECL(alSourceUnqueueBuffers),
-    DECL(alGenBuffers),
-    DECL(alDeleteBuffers),
-    DECL(alIsBuffer),
-    DECL(alBufferData),
-    DECL(alBufferf),
-    DECL(alBuffer3f),
-    DECL(alBufferfv),
-    DECL(alBufferi),
-    DECL(alBuffer3i),
-    DECL(alBufferiv),
-    DECL(alGetBufferf),
-    DECL(alGetBuffer3f),
-    DECL(alGetBufferfv),
-    DECL(alGetBufferi),
-    DECL(alGetBuffer3i),
-    DECL(alGetBufferiv),
-    DECL(alDopplerFactor),
-    DECL(alDopplerVelocity),
-    DECL(alSpeedOfSound),
-    DECL(alDistanceModel),
-
-    DECL(alGenFilters),
-    DECL(alDeleteFilters),
-    DECL(alIsFilter),
-    DECL(alFilteri),
-    DECL(alFilteriv),
-    DECL(alFilterf),
-    DECL(alFilterfv),
-    DECL(alGetFilteri),
-    DECL(alGetFilteriv),
-    DECL(alGetFilterf),
-    DECL(alGetFilterfv),
-    DECL(alGenEffects),
-    DECL(alDeleteEffects),
-    DECL(alIsEffect),
-    DECL(alEffecti),
-    DECL(alEffectiv),
-    DECL(alEffectf),
-    DECL(alEffectfv),
-    DECL(alGetEffecti),
-    DECL(alGetEffectiv),
-    DECL(alGetEffectf),
-    DECL(alGetEffectfv),
-    DECL(alGenAuxiliaryEffectSlots),
-    DECL(alDeleteAuxiliaryEffectSlots),
-    DECL(alIsAuxiliaryEffectSlot),
-    DECL(alAuxiliaryEffectSloti),
-    DECL(alAuxiliaryEffectSlotiv),
-    DECL(alAuxiliaryEffectSlotf),
-    DECL(alAuxiliaryEffectSlotfv),
-    DECL(alGetAuxiliaryEffectSloti),
-    DECL(alGetAuxiliaryEffectSlotiv),
-    DECL(alGetAuxiliaryEffectSlotf),
-    DECL(alGetAuxiliaryEffectSlotfv),
-
-    DECL(alDeferUpdatesSOFT),
-    DECL(alProcessUpdatesSOFT),
-
-    DECL(alSourcedSOFT),
-    DECL(alSource3dSOFT),
-    DECL(alSourcedvSOFT),
-    DECL(alGetSourcedSOFT),
-    DECL(alGetSource3dSOFT),
-    DECL(alGetSourcedvSOFT),
-    DECL(alSourcei64SOFT),
-    DECL(alSource3i64SOFT),
-    DECL(alSourcei64vSOFT),
-    DECL(alGetSourcei64SOFT),
-    DECL(alGetSource3i64SOFT),
-    DECL(alGetSourcei64vSOFT),
-
-    DECL(alGetStringiSOFT),
-
-    DECL(alBufferStorageSOFT),
-    DECL(alMapBufferSOFT),
-    DECL(alUnmapBufferSOFT),
-    DECL(alFlushMappedBufferSOFT),
-
-    DECL(alEventControlSOFT),
-    DECL(alEventCallbackSOFT),
-    DECL(alGetPointerSOFT),
-    DECL(alGetPointervSOFT),
-};
-#undef DECL
-
-#define DECL(x) { #x, (x) }
-static const struct {
-    const ALCchar *enumName;
-    ALCenum value;
-} alcEnumerations[] = {
-    DECL(ALC_INVALID),
-    DECL(ALC_FALSE),
-    DECL(ALC_TRUE),
-
-    DECL(ALC_MAJOR_VERSION),
-    DECL(ALC_MINOR_VERSION),
-    DECL(ALC_ATTRIBUTES_SIZE),
-    DECL(ALC_ALL_ATTRIBUTES),
-    DECL(ALC_DEFAULT_DEVICE_SPECIFIER),
-    DECL(ALC_DEVICE_SPECIFIER),
-    DECL(ALC_ALL_DEVICES_SPECIFIER),
-    DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER),
-    DECL(ALC_EXTENSIONS),
-    DECL(ALC_FREQUENCY),
-    DECL(ALC_REFRESH),
-    DECL(ALC_SYNC),
-    DECL(ALC_MONO_SOURCES),
-    DECL(ALC_STEREO_SOURCES),
-    DECL(ALC_CAPTURE_DEVICE_SPECIFIER),
-    DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER),
-    DECL(ALC_CAPTURE_SAMPLES),
-    DECL(ALC_CONNECTED),
-
-    DECL(ALC_EFX_MAJOR_VERSION),
-    DECL(ALC_EFX_MINOR_VERSION),
-    DECL(ALC_MAX_AUXILIARY_SENDS),
-
-    DECL(ALC_FORMAT_CHANNELS_SOFT),
-    DECL(ALC_FORMAT_TYPE_SOFT),
-
-    DECL(ALC_MONO_SOFT),
-    DECL(ALC_STEREO_SOFT),
-    DECL(ALC_QUAD_SOFT),
-    DECL(ALC_5POINT1_SOFT),
-    DECL(ALC_6POINT1_SOFT),
-    DECL(ALC_7POINT1_SOFT),
-    DECL(ALC_BFORMAT3D_SOFT),
-
-    DECL(ALC_BYTE_SOFT),
-    DECL(ALC_UNSIGNED_BYTE_SOFT),
-    DECL(ALC_SHORT_SOFT),
-    DECL(ALC_UNSIGNED_SHORT_SOFT),
-    DECL(ALC_INT_SOFT),
-    DECL(ALC_UNSIGNED_INT_SOFT),
-    DECL(ALC_FLOAT_SOFT),
-
-    DECL(ALC_HRTF_SOFT),
-    DECL(ALC_DONT_CARE_SOFT),
-    DECL(ALC_HRTF_STATUS_SOFT),
-    DECL(ALC_HRTF_DISABLED_SOFT),
-    DECL(ALC_HRTF_ENABLED_SOFT),
-    DECL(ALC_HRTF_DENIED_SOFT),
-    DECL(ALC_HRTF_REQUIRED_SOFT),
-    DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT),
-    DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT),
-    DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT),
-    DECL(ALC_HRTF_SPECIFIER_SOFT),
-    DECL(ALC_HRTF_ID_SOFT),
-
-    DECL(ALC_AMBISONIC_LAYOUT_SOFT),
-    DECL(ALC_AMBISONIC_SCALING_SOFT),
-    DECL(ALC_AMBISONIC_ORDER_SOFT),
-    DECL(ALC_ACN_SOFT),
-    DECL(ALC_FUMA_SOFT),
-    DECL(ALC_N3D_SOFT),
-    DECL(ALC_SN3D_SOFT),
-
-    DECL(ALC_OUTPUT_LIMITER_SOFT),
-
-    DECL(ALC_NO_ERROR),
-    DECL(ALC_INVALID_DEVICE),
-    DECL(ALC_INVALID_CONTEXT),
-    DECL(ALC_INVALID_ENUM),
-    DECL(ALC_INVALID_VALUE),
-    DECL(ALC_OUT_OF_MEMORY),
-
-
-    DECL(AL_INVALID),
-    DECL(AL_NONE),
-    DECL(AL_FALSE),
-    DECL(AL_TRUE),
-
-    DECL(AL_SOURCE_RELATIVE),
-    DECL(AL_CONE_INNER_ANGLE),
-    DECL(AL_CONE_OUTER_ANGLE),
-    DECL(AL_PITCH),
-    DECL(AL_POSITION),
-    DECL(AL_DIRECTION),
-    DECL(AL_VELOCITY),
-    DECL(AL_LOOPING),
-    DECL(AL_BUFFER),
-    DECL(AL_GAIN),
-    DECL(AL_MIN_GAIN),
-    DECL(AL_MAX_GAIN),
-    DECL(AL_ORIENTATION),
-    DECL(AL_REFERENCE_DISTANCE),
-    DECL(AL_ROLLOFF_FACTOR),
-    DECL(AL_CONE_OUTER_GAIN),
-    DECL(AL_MAX_DISTANCE),
-    DECL(AL_SEC_OFFSET),
-    DECL(AL_SAMPLE_OFFSET),
-    DECL(AL_BYTE_OFFSET),
-    DECL(AL_SOURCE_TYPE),
-    DECL(AL_STATIC),
-    DECL(AL_STREAMING),
-    DECL(AL_UNDETERMINED),
-    DECL(AL_METERS_PER_UNIT),
-    DECL(AL_LOOP_POINTS_SOFT),
-    DECL(AL_DIRECT_CHANNELS_SOFT),
-
-    DECL(AL_DIRECT_FILTER),
-    DECL(AL_AUXILIARY_SEND_FILTER),
-    DECL(AL_AIR_ABSORPTION_FACTOR),
-    DECL(AL_ROOM_ROLLOFF_FACTOR),
-    DECL(AL_CONE_OUTER_GAINHF),
-    DECL(AL_DIRECT_FILTER_GAINHF_AUTO),
-    DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO),
-    DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO),
-
-    DECL(AL_SOURCE_STATE),
-    DECL(AL_INITIAL),
-    DECL(AL_PLAYING),
-    DECL(AL_PAUSED),
-    DECL(AL_STOPPED),
-
-    DECL(AL_BUFFERS_QUEUED),
-    DECL(AL_BUFFERS_PROCESSED),
-
-    DECL(AL_FORMAT_MONO8),
-    DECL(AL_FORMAT_MONO16),
-    DECL(AL_FORMAT_MONO_FLOAT32),
-    DECL(AL_FORMAT_MONO_DOUBLE_EXT),
-    DECL(AL_FORMAT_STEREO8),
-    DECL(AL_FORMAT_STEREO16),
-    DECL(AL_FORMAT_STEREO_FLOAT32),
-    DECL(AL_FORMAT_STEREO_DOUBLE_EXT),
-    DECL(AL_FORMAT_MONO_IMA4),
-    DECL(AL_FORMAT_STEREO_IMA4),
-    DECL(AL_FORMAT_MONO_MSADPCM_SOFT),
-    DECL(AL_FORMAT_STEREO_MSADPCM_SOFT),
-    DECL(AL_FORMAT_QUAD8_LOKI),
-    DECL(AL_FORMAT_QUAD16_LOKI),
-    DECL(AL_FORMAT_QUAD8),
-    DECL(AL_FORMAT_QUAD16),
-    DECL(AL_FORMAT_QUAD32),
-    DECL(AL_FORMAT_51CHN8),
-    DECL(AL_FORMAT_51CHN16),
-    DECL(AL_FORMAT_51CHN32),
-    DECL(AL_FORMAT_61CHN8),
-    DECL(AL_FORMAT_61CHN16),
-    DECL(AL_FORMAT_61CHN32),
-    DECL(AL_FORMAT_71CHN8),
-    DECL(AL_FORMAT_71CHN16),
-    DECL(AL_FORMAT_71CHN32),
-    DECL(AL_FORMAT_REAR8),
-    DECL(AL_FORMAT_REAR16),
-    DECL(AL_FORMAT_REAR32),
-    DECL(AL_FORMAT_MONO_MULAW),
-    DECL(AL_FORMAT_MONO_MULAW_EXT),
-    DECL(AL_FORMAT_STEREO_MULAW),
-    DECL(AL_FORMAT_STEREO_MULAW_EXT),
-    DECL(AL_FORMAT_QUAD_MULAW),
-    DECL(AL_FORMAT_51CHN_MULAW),
-    DECL(AL_FORMAT_61CHN_MULAW),
-    DECL(AL_FORMAT_71CHN_MULAW),
-    DECL(AL_FORMAT_REAR_MULAW),
-    DECL(AL_FORMAT_MONO_ALAW_EXT),
-    DECL(AL_FORMAT_STEREO_ALAW_EXT),
-
-    DECL(AL_FORMAT_BFORMAT2D_8),
-    DECL(AL_FORMAT_BFORMAT2D_16),
-    DECL(AL_FORMAT_BFORMAT2D_FLOAT32),
-    DECL(AL_FORMAT_BFORMAT2D_MULAW),
-    DECL(AL_FORMAT_BFORMAT3D_8),
-    DECL(AL_FORMAT_BFORMAT3D_16),
-    DECL(AL_FORMAT_BFORMAT3D_FLOAT32),
-    DECL(AL_FORMAT_BFORMAT3D_MULAW),
-
-    DECL(AL_FREQUENCY),
-    DECL(AL_BITS),
-    DECL(AL_CHANNELS),
-    DECL(AL_SIZE),
-    DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT),
-    DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT),
-
-    DECL(AL_SOURCE_RADIUS),
-
-    DECL(AL_STEREO_ANGLES),
-
-    DECL(AL_UNUSED),
-    DECL(AL_PENDING),
-    DECL(AL_PROCESSED),
-
-    DECL(AL_NO_ERROR),
-    DECL(AL_INVALID_NAME),
-    DECL(AL_INVALID_ENUM),
-    DECL(AL_INVALID_VALUE),
-    DECL(AL_INVALID_OPERATION),
-    DECL(AL_OUT_OF_MEMORY),
-
-    DECL(AL_VENDOR),
-    DECL(AL_VERSION),
-    DECL(AL_RENDERER),
-    DECL(AL_EXTENSIONS),
-
-    DECL(AL_DOPPLER_FACTOR),
-    DECL(AL_DOPPLER_VELOCITY),
-    DECL(AL_DISTANCE_MODEL),
-    DECL(AL_SPEED_OF_SOUND),
-    DECL(AL_SOURCE_DISTANCE_MODEL),
-    DECL(AL_DEFERRED_UPDATES_SOFT),
-    DECL(AL_GAIN_LIMIT_SOFT),
-
-    DECL(AL_INVERSE_DISTANCE),
-    DECL(AL_INVERSE_DISTANCE_CLAMPED),
-    DECL(AL_LINEAR_DISTANCE),
-    DECL(AL_LINEAR_DISTANCE_CLAMPED),
-    DECL(AL_EXPONENT_DISTANCE),
-    DECL(AL_EXPONENT_DISTANCE_CLAMPED),
-
-    DECL(AL_FILTER_TYPE),
-    DECL(AL_FILTER_NULL),
-    DECL(AL_FILTER_LOWPASS),
-    DECL(AL_FILTER_HIGHPASS),
-    DECL(AL_FILTER_BANDPASS),
-
-    DECL(AL_LOWPASS_GAIN),
-    DECL(AL_LOWPASS_GAINHF),
-
-    DECL(AL_HIGHPASS_GAIN),
-    DECL(AL_HIGHPASS_GAINLF),
-
-    DECL(AL_BANDPASS_GAIN),
-    DECL(AL_BANDPASS_GAINHF),
-    DECL(AL_BANDPASS_GAINLF),
-
-    DECL(AL_EFFECT_TYPE),
-    DECL(AL_EFFECT_NULL),
-    DECL(AL_EFFECT_REVERB),
-    DECL(AL_EFFECT_EAXREVERB),
-    DECL(AL_EFFECT_CHORUS),
-    DECL(AL_EFFECT_DISTORTION),
-    DECL(AL_EFFECT_ECHO),
-    DECL(AL_EFFECT_FLANGER),
-    DECL(AL_EFFECT_PITCH_SHIFTER),
-#if 0
-    DECL(AL_EFFECT_FREQUENCY_SHIFTER),
-    DECL(AL_EFFECT_VOCAL_MORPHER),
-#endif
-    DECL(AL_EFFECT_RING_MODULATOR),
-#if 0
-    DECL(AL_EFFECT_AUTOWAH),
-#endif
-    DECL(AL_EFFECT_COMPRESSOR),
-    DECL(AL_EFFECT_EQUALIZER),
-    DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT),
-    DECL(AL_EFFECT_DEDICATED_DIALOGUE),
-
-    DECL(AL_EFFECTSLOT_EFFECT),
-    DECL(AL_EFFECTSLOT_GAIN),
-    DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO),
-    DECL(AL_EFFECTSLOT_NULL),
-
-    DECL(AL_EAXREVERB_DENSITY),
-    DECL(AL_EAXREVERB_DIFFUSION),
-    DECL(AL_EAXREVERB_GAIN),
-    DECL(AL_EAXREVERB_GAINHF),
-    DECL(AL_EAXREVERB_GAINLF),
-    DECL(AL_EAXREVERB_DECAY_TIME),
-    DECL(AL_EAXREVERB_DECAY_HFRATIO),
-    DECL(AL_EAXREVERB_DECAY_LFRATIO),
-    DECL(AL_EAXREVERB_REFLECTIONS_GAIN),
-    DECL(AL_EAXREVERB_REFLECTIONS_DELAY),
-    DECL(AL_EAXREVERB_REFLECTIONS_PAN),
-    DECL(AL_EAXREVERB_LATE_REVERB_GAIN),
-    DECL(AL_EAXREVERB_LATE_REVERB_DELAY),
-    DECL(AL_EAXREVERB_LATE_REVERB_PAN),
-    DECL(AL_EAXREVERB_ECHO_TIME),
-    DECL(AL_EAXREVERB_ECHO_DEPTH),
-    DECL(AL_EAXREVERB_MODULATION_TIME),
-    DECL(AL_EAXREVERB_MODULATION_DEPTH),
-    DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF),
-    DECL(AL_EAXREVERB_HFREFERENCE),
-    DECL(AL_EAXREVERB_LFREFERENCE),
-    DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR),
-    DECL(AL_EAXREVERB_DECAY_HFLIMIT),
-
-    DECL(AL_REVERB_DENSITY),
-    DECL(AL_REVERB_DIFFUSION),
-    DECL(AL_REVERB_GAIN),
-    DECL(AL_REVERB_GAINHF),
-    DECL(AL_REVERB_DECAY_TIME),
-    DECL(AL_REVERB_DECAY_HFRATIO),
-    DECL(AL_REVERB_REFLECTIONS_GAIN),
-    DECL(AL_REVERB_REFLECTIONS_DELAY),
-    DECL(AL_REVERB_LATE_REVERB_GAIN),
-    DECL(AL_REVERB_LATE_REVERB_DELAY),
-    DECL(AL_REVERB_AIR_ABSORPTION_GAINHF),
-    DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR),
-    DECL(AL_REVERB_DECAY_HFLIMIT),
-
-    DECL(AL_CHORUS_WAVEFORM),
-    DECL(AL_CHORUS_PHASE),
-    DECL(AL_CHORUS_RATE),
-    DECL(AL_CHORUS_DEPTH),
-    DECL(AL_CHORUS_FEEDBACK),
-    DECL(AL_CHORUS_DELAY),
-
-    DECL(AL_DISTORTION_EDGE),
-    DECL(AL_DISTORTION_GAIN),
-    DECL(AL_DISTORTION_LOWPASS_CUTOFF),
-    DECL(AL_DISTORTION_EQCENTER),
-    DECL(AL_DISTORTION_EQBANDWIDTH),
-
-    DECL(AL_ECHO_DELAY),
-    DECL(AL_ECHO_LRDELAY),
-    DECL(AL_ECHO_DAMPING),
-    DECL(AL_ECHO_FEEDBACK),
-    DECL(AL_ECHO_SPREAD),
-
-    DECL(AL_FLANGER_WAVEFORM),
-    DECL(AL_FLANGER_PHASE),
-    DECL(AL_FLANGER_RATE),
-    DECL(AL_FLANGER_DEPTH),
-    DECL(AL_FLANGER_FEEDBACK),
-    DECL(AL_FLANGER_DELAY),
-
-    DECL(AL_RING_MODULATOR_FREQUENCY),
-    DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF),
-    DECL(AL_RING_MODULATOR_WAVEFORM),
-
-    DECL(AL_PITCH_SHIFTER_COARSE_TUNE),
-    DECL(AL_PITCH_SHIFTER_FINE_TUNE),
-
-    DECL(AL_COMPRESSOR_ONOFF),
-
-    DECL(AL_EQUALIZER_LOW_GAIN),
-    DECL(AL_EQUALIZER_LOW_CUTOFF),
-    DECL(AL_EQUALIZER_MID1_GAIN),
-    DECL(AL_EQUALIZER_MID1_CENTER),
-    DECL(AL_EQUALIZER_MID1_WIDTH),
-    DECL(AL_EQUALIZER_MID2_GAIN),
-    DECL(AL_EQUALIZER_MID2_CENTER),
-    DECL(AL_EQUALIZER_MID2_WIDTH),
-    DECL(AL_EQUALIZER_HIGH_GAIN),
-    DECL(AL_EQUALIZER_HIGH_CUTOFF),
-
-    DECL(AL_DEDICATED_GAIN),
-
-    DECL(AL_NUM_RESAMPLERS_SOFT),
-    DECL(AL_DEFAULT_RESAMPLER_SOFT),
-    DECL(AL_SOURCE_RESAMPLER_SOFT),
-    DECL(AL_RESAMPLER_NAME_SOFT),
-
-    DECL(AL_SOURCE_SPATIALIZE_SOFT),
-    DECL(AL_AUTO_SOFT),
-
-    DECL(AL_MAP_READ_BIT_SOFT),
-    DECL(AL_MAP_WRITE_BIT_SOFT),
-    DECL(AL_MAP_PERSISTENT_BIT_SOFT),
-    DECL(AL_PRESERVE_DATA_BIT_SOFT),
-
-    DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT),
-    DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT),
-    DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT),
-    DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT),
-    DECL(AL_EVENT_TYPE_ERROR_SOFT),
-    DECL(AL_EVENT_TYPE_PERFORMANCE_SOFT),
-    DECL(AL_EVENT_TYPE_DEPRECATED_SOFT),
-};
-#undef DECL
-
-static const ALCchar alcNoError[] = "No Error";
-static const ALCchar alcErrInvalidDevice[] = "Invalid Device";
-static const ALCchar alcErrInvalidContext[] = "Invalid Context";
-static const ALCchar alcErrInvalidEnum[] = "Invalid Enum";
-static const ALCchar alcErrInvalidValue[] = "Invalid Value";
-static const ALCchar alcErrOutOfMemory[] = "Out of Memory";
-
-
-/************************************************
- * Global variables
- ************************************************/
-
-/* Enumerated device names */
-static const ALCchar alcDefaultName[] = "OpenAL Soft\0";
-
-static al_string alcAllDevicesList;
-static al_string alcCaptureDeviceList;
-
-/* Default is always the first in the list */
-static ALCchar *alcDefaultAllDevicesSpecifier;
-static ALCchar *alcCaptureDefaultDeviceSpecifier;
-
-/* Default context extensions */
-static const 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_block_alignment "
-    "AL_SOFT_deferred_updates "
-    "AL_SOFT_direct_channels "
-    "AL_SOFTX_events "
-    "AL_SOFT_gain_clamp_ex "
-    "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";
-
-static ATOMIC(ALCenum) LastNullDeviceError = ATOMIC_INIT_STATIC(ALC_NO_ERROR);
-
-/* Thread-local current context */
-static altss_t LocalContext;
-/* Process-wide current context */
-static ATOMIC(ALCcontext*) GlobalContext = ATOMIC_INIT_STATIC(NULL);
-
-/* Mixing thread piority level */
-ALint RTPrioLevel;
-
-FILE *LogFile;
-#ifdef _DEBUG
-enum LogLevel LogLevel = LogWarning;
-#else
-enum LogLevel LogLevel = LogError;
-#endif
-
-/* Flag to trap ALC device errors */
-static ALCboolean TrapALCError = ALC_FALSE;
-
-/* One-time configuration init control */
-static alonce_flag alc_config_once = AL_ONCE_FLAG_INIT;
-
-/* Default effect that applies to sources that don't have an effect on send 0 */
-static ALeffect DefaultEffect;
-
-/* Flag to specify if alcSuspendContext/alcProcessContext should defer/process
- * updates.
- */
-static ALCboolean SuspendDefers = ALC_TRUE;
-
-
-/************************************************
- * ALC information
- ************************************************/
-static const ALCchar alcNoDeviceExtList[] =
-    "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE "
-    "ALC_EXT_thread_local_context ALC_SOFT_loopback";
-static const ALCchar alcExtensionList[] =
-    "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE "
-    "ALC_EXT_DEDICATED ALC_EXT_disconnect ALC_EXT_EFX "
-    "ALC_EXT_thread_local_context ALC_SOFT_device_clock ALC_SOFT_HRTF "
-    "ALC_SOFT_loopback ALC_SOFT_output_limiter ALC_SOFT_pause_device";
-static const ALCint alcMajorVersion = 1;
-static const ALCint alcMinorVersion = 1;
-
-static const ALCint alcEFXMajorVersion = 1;
-static const ALCint alcEFXMinorVersion = 0;
-
-
-/************************************************
- * Device lists
- ************************************************/
-static ATOMIC(ALCdevice*) DeviceList = ATOMIC_INIT_STATIC(NULL);
-
-static almtx_t ListLock;
-static inline void LockLists(void)
-{
-    int ret = almtx_lock(&ListLock);
-    assert(ret == althrd_success);
-}
-static inline void UnlockLists(void)
-{
-    int ret = almtx_unlock(&ListLock);
-    assert(ret == althrd_success);
-}
-
-/************************************************
- * Library initialization
- ************************************************/
-#if defined(_WIN32)
-static void alc_init(void);
-static void alc_deinit(void);
-static void alc_deinit_safe(void);
-
-#ifndef AL_LIBTYPE_STATIC
-BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD reason, LPVOID lpReserved)
-{
-    switch(reason)
-    {
-        case DLL_PROCESS_ATTACH:
-            /* Pin the DLL so we won't get unloaded until the process terminates */
-            GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
-                               (WCHAR*)hModule, &hModule);
-            alc_init();
-            break;
-
-        case DLL_THREAD_DETACH:
-            althrd_thread_detach();
-            break;
-
-        case DLL_PROCESS_DETACH:
-            if(!lpReserved)
-                alc_deinit();
-            else
-                alc_deinit_safe();
-            break;
-    }
-    return TRUE;
-}
-#elif defined(_MSC_VER)
-#pragma section(".CRT$XCU",read)
-static void alc_constructor(void);
-static void alc_destructor(void);
-__declspec(allocate(".CRT$XCU")) void (__cdecl* alc_constructor_)(void) = alc_constructor;
-
-static void alc_constructor(void)
-{
-    atexit(alc_destructor);
-    alc_init();
-}
-
-static void alc_destructor(void)
-{
-    alc_deinit();
-}
-#elif defined(HAVE_GCC_DESTRUCTOR)
-static void alc_init(void) __attribute__((constructor));
-static void alc_deinit(void) __attribute__((destructor));
-#else
-#error "No static initialization available on this platform!"
-#endif
-
-#elif defined(HAVE_GCC_DESTRUCTOR)
-
-static void alc_init(void) __attribute__((constructor));
-static void alc_deinit(void) __attribute__((destructor));
-
-#else
-#error "No global initialization available on this platform!"
-#endif
-
-static void ReleaseThreadCtx(void *ptr);
-static void alc_init(void)
-{
-    const char *str;
-    int ret;
-
-    LogFile = stderr;
-
-    AL_STRING_INIT(alcAllDevicesList);
-    AL_STRING_INIT(alcCaptureDeviceList);
-
-    str = getenv("__ALSOFT_HALF_ANGLE_CONES");
-    if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-        ConeScale *= 0.5f;
-
-    str = getenv("__ALSOFT_REVERSE_Z");
-    if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-        ZScale *= -1.0f;
-
-    str = getenv("__ALSOFT_REVERB_IGNORES_SOUND_SPEED");
-    if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-        OverrideReverbSpeedOfSound = AL_TRUE;
-
-    ret = altss_create(&LocalContext, ReleaseThreadCtx);
-    assert(ret == althrd_success);
-
-    ret = almtx_init(&ListLock, almtx_recursive);
-    assert(ret == althrd_success);
-}
-
-static void alc_initconfig(void)
-{
-    const char *devs, *str;
-    int capfilter;
-    float valf;
-    int i, n;
-
-    str = getenv("ALSOFT_LOGLEVEL");
-    if(str)
-    {
-        long lvl = strtol(str, NULL, 0);
-        if(lvl >= NoLog && lvl <= LogRef)
-            LogLevel = lvl;
-    }
-
-    str = getenv("ALSOFT_LOGFILE");
-    if(str && str[0])
-    {
-        FILE *logfile = al_fopen(str, "wt");
-        if(logfile) LogFile = logfile;
-        else ERR("Failed to open log file '%s'\n", str);
-    }
-
-    TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION,
-          ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH);
-    {
-        char buf[1024] = "";
-        int len = 0;
-
-        if(BackendListSize > 0)
-            len += snprintf(buf, sizeof(buf), "%s", BackendList[0].name);
-        for(i = 1;i < BackendListSize;i++)
-            len += snprintf(buf+len, sizeof(buf)-len, ", %s", BackendList[i].name);
-        TRACE("Supported backends: %s\n", buf);
-    }
-    ReadALConfig();
-
-    str = getenv("__ALSOFT_SUSPEND_CONTEXT");
-    if(str && *str)
-    {
-        if(strcasecmp(str, "ignore") == 0)
-        {
-            SuspendDefers = ALC_FALSE;
-            TRACE("Selected context suspend behavior, \"ignore\"\n");
-        }
-        else
-            ERR("Unhandled context suspend behavior setting: \"%s\"\n", str);
-    }
-
-    capfilter = 0;
-#if defined(HAVE_SSE4_1)
-    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
-#elif defined(HAVE_SSE3)
-    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
-#elif defined(HAVE_SSE2)
-    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2;
-#elif defined(HAVE_SSE)
-    capfilter |= CPU_CAP_SSE;
-#endif
-#ifdef HAVE_NEON
-    capfilter |= CPU_CAP_NEON;
-#endif
-    if(ConfigValueStr(NULL, NULL, "disable-cpu-exts", &str))
-    {
-        if(strcasecmp(str, "all") == 0)
-            capfilter = 0;
-        else
-        {
-            size_t len;
-            const char *next = str;
-
-            do {
-                str = next;
-                while(isspace(str[0]))
-                    str++;
-                next = strchr(str, ',');
-
-                if(!str[0] || str[0] == ',')
-                    continue;
-
-                len = (next ? ((size_t)(next-str)) : strlen(str));
-                while(len > 0 && isspace(str[len-1]))
-                    len--;
-                if(len == 3 && strncasecmp(str, "sse", len) == 0)
-                    capfilter &= ~CPU_CAP_SSE;
-                else if(len == 4 && strncasecmp(str, "sse2", len) == 0)
-                    capfilter &= ~CPU_CAP_SSE2;
-                else if(len == 4 && strncasecmp(str, "sse3", len) == 0)
-                    capfilter &= ~CPU_CAP_SSE3;
-                else if(len == 6 && strncasecmp(str, "sse4.1", len) == 0)
-                    capfilter &= ~CPU_CAP_SSE4_1;
-                else if(len == 4 && strncasecmp(str, "neon", len) == 0)
-                    capfilter &= ~CPU_CAP_NEON;
-                else
-                    WARN("Invalid CPU extension \"%s\"\n", str);
-            } while(next++);
-        }
-    }
-    FillCPUCaps(capfilter);
-
-#ifdef _WIN32
-    RTPrioLevel = 1;
-#else
-    RTPrioLevel = 0;
-#endif
-    ConfigValueInt(NULL, NULL, "rt-prio", &RTPrioLevel);
-
-    aluInit();
-    aluInitMixer();
-
-    str = getenv("ALSOFT_TRAP_ERROR");
-    if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-    {
-        TrapALError  = AL_TRUE;
-        TrapALCError = AL_TRUE;
-    }
-    else
-    {
-        str = getenv("ALSOFT_TRAP_AL_ERROR");
-        if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-            TrapALError = AL_TRUE;
-        TrapALError = GetConfigValueBool(NULL, NULL, "trap-al-error", TrapALError);
-
-        str = getenv("ALSOFT_TRAP_ALC_ERROR");
-        if(str && (strcasecmp(str, "true") == 0 || strtol(str, NULL, 0) == 1))
-            TrapALCError = ALC_TRUE;
-        TrapALCError = GetConfigValueBool(NULL, NULL, "trap-alc-error", TrapALCError);
-    }
-
-    if(ConfigValueFloat(NULL, "reverb", "boost", &valf))
-        ReverbBoost *= powf(10.0f, valf / 20.0f);
-
-    if(((devs=getenv("ALSOFT_DRIVERS")) && devs[0]) ||
-       ConfigValueStr(NULL, NULL, "drivers", &devs))
-    {
-        int n;
-        size_t len;
-        const char *next = devs;
-        int endlist, delitem;
-
-        i = 0;
-        do {
-            devs = next;
-            while(isspace(devs[0]))
-                devs++;
-            next = strchr(devs, ',');
-
-            delitem = (devs[0] == '-');
-            if(devs[0] == '-') devs++;
-
-            if(!devs[0] || devs[0] == ',')
-            {
-                endlist = 0;
-                continue;
-            }
-            endlist = 1;
-
-            len = (next ? ((size_t)(next-devs)) : strlen(devs));
-            while(len > 0 && isspace(devs[len-1]))
-                len--;
-#ifdef HAVE_WASAPI
-            /* HACK: For backwards compatibility, convert backend references of
-             * mmdevapi to wasapi. This should eventually be removed.
-             */
-            if(len == 8 && strncmp(devs, "mmdevapi", len) == 0)
-            {
-                devs = "wasapi";
-                len = 6;
-            }
-#endif
-            for(n = i;n < BackendListSize;n++)
-            {
-                if(len == strlen(BackendList[n].name) &&
-                   strncmp(BackendList[n].name, devs, len) == 0)
-                {
-                    if(delitem)
-                    {
-                        for(;n+1 < BackendListSize;n++)
-                            BackendList[n] = BackendList[n+1];
-                        BackendListSize--;
-                    }
-                    else
-                    {
-                        struct BackendInfo Bkp = BackendList[n];
-                        for(;n > i;n--)
-                            BackendList[n] = BackendList[n-1];
-                        BackendList[n] = Bkp;
-
-                        i++;
-                    }
-                    break;
-                }
-            }
-        } while(next++);
-
-        if(endlist)
-            BackendListSize = i;
-    }
-
-    for(n = i = 0;i < BackendListSize && (!PlaybackBackend.name || !CaptureBackend.name);i++)
-    {
-        ALCbackendFactory *factory;
-        BackendList[n] = BackendList[i];
-
-        factory = BackendList[n].getFactory();
-        if(!V0(factory,init)())
-        {
-            WARN("Failed to initialize backend \"%s\"\n", BackendList[n].name);
-            continue;
-        }
-
-        TRACE("Initialized backend \"%s\"\n", BackendList[n].name);
-        if(!PlaybackBackend.name && V(factory,querySupport)(ALCbackend_Playback))
-        {
-            PlaybackBackend = BackendList[n];
-            TRACE("Added \"%s\" for playback\n", PlaybackBackend.name);
-        }
-        if(!CaptureBackend.name && V(factory,querySupport)(ALCbackend_Capture))
-        {
-            CaptureBackend = BackendList[n];
-            TRACE("Added \"%s\" for capture\n", CaptureBackend.name);
-        }
-        n++;
-    }
-    BackendListSize = n;
-
-    {
-        ALCbackendFactory *factory = ALCloopbackFactory_getFactory();
-        V0(factory,init)();
-    }
-
-    if(!PlaybackBackend.name)
-        WARN("No playback backend available!\n");
-    if(!CaptureBackend.name)
-        WARN("No capture backend available!\n");
-
-    if(ConfigValueStr(NULL, NULL, "excludefx", &str))
-    {
-        size_t len;
-        const char *next = str;
-
-        do {
-            str = next;
-            next = strchr(str, ',');
-
-            if(!str[0] || next == str)
-                continue;
-
-            len = (next ? ((size_t)(next-str)) : strlen(str));
-            for(n = 0;n < EFFECTLIST_SIZE;n++)
-            {
-                if(len == strlen(EffectList[n].name) &&
-                   strncmp(EffectList[n].name, str, len) == 0)
-                    DisabledEffects[EffectList[n].type] = AL_TRUE;
-            }
-        } while(next++);
-    }
-
-    InitEffect(&DefaultEffect);
-    str = getenv("ALSOFT_DEFAULT_REVERB");
-    if((str && str[0]) || ConfigValueStr(NULL, NULL, "default-reverb", &str))
-        LoadReverbPreset(str, &DefaultEffect);
-}
-#define DO_INITCONFIG() alcall_once(&alc_config_once, alc_initconfig)
-
-#ifdef __ANDROID__
-#include <jni.h>
-
-static JavaVM *gJavaVM;
-static pthread_key_t gJVMThreadKey;
-
-static void CleanupJNIEnv(void* UNUSED(ptr))
-{
-    JCALL0(gJavaVM,DetachCurrentThread)();
-}
-
-void *Android_GetJNIEnv(void)
-{
-    if(!gJavaVM)
-    {
-        WARN("gJavaVM is NULL!\n");
-        return NULL;
-    }
-
-    /* http://developer.android.com/guide/practices/jni.html
-     *
-     * All threads are Linux threads, scheduled by the kernel. They're usually
-     * started from managed code (using Thread.start), but they can also be
-     * created elsewhere and then attached to the JavaVM. For example, a thread
-     * started with pthread_create can be attached with the JNI
-     * AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a
-     * thread is attached, it has no JNIEnv, and cannot make JNI calls.
-     * Attaching a natively-created thread causes a java.lang.Thread object to
-     * be constructed and added to the "main" ThreadGroup, making it visible to
-     * the debugger. Calling AttachCurrentThread on an already-attached thread
-     * is a no-op.
-     */
-    JNIEnv *env = pthread_getspecific(gJVMThreadKey);
-    if(!env)
-    {
-        int status = JCALL(gJavaVM,AttachCurrentThread)(&env, NULL);
-        if(status < 0)
-        {
-            ERR("Failed to attach current thread\n");
-            return NULL;
-        }
-        pthread_setspecific(gJVMThreadKey, env);
-    }
-    return env;
-}
-
-/* Automatically called by JNI. */
-JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* UNUSED(reserved))
-{
-    void *env;
-    int err;
-
-    gJavaVM = jvm;
-    if(JCALL(gJavaVM,GetEnv)(&env, JNI_VERSION_1_4) != JNI_OK)
-    {
-        ERR("Failed to get JNIEnv with JNI_VERSION_1_4\n");
-        return JNI_ERR;
-    }
-
-    /* Create gJVMThreadKey so we can keep track of the JNIEnv assigned to each
-     * thread. The JNIEnv *must* be detached before the thread is destroyed.
-     */
-    if((err=pthread_key_create(&gJVMThreadKey, CleanupJNIEnv)) != 0)
-        ERR("pthread_key_create failed: %d\n", err);
-    pthread_setspecific(gJVMThreadKey, env);
-    return JNI_VERSION_1_4;
-}
-#endif
-
-
-/************************************************
- * Library deinitialization
- ************************************************/
-static void alc_cleanup(void)
-{
-    ALCdevice *dev;
-
-    AL_STRING_DEINIT(alcAllDevicesList);
-    AL_STRING_DEINIT(alcCaptureDeviceList);
-
-    free(alcDefaultAllDevicesSpecifier);
-    alcDefaultAllDevicesSpecifier = NULL;
-    free(alcCaptureDefaultDeviceSpecifier);
-    alcCaptureDefaultDeviceSpecifier = NULL;
-
-    if((dev=ATOMIC_EXCHANGE_PTR_SEQ(&DeviceList, NULL)) != NULL)
-    {
-        ALCuint num = 0;
-        do {
-            num++;
-            dev = ATOMIC_LOAD(&dev->next, almemory_order_relaxed);
-        } while(dev != NULL);
-        ERR("%u device%s not closed\n", num, (num>1)?"s":"");
-    }
-}
-
-static void alc_deinit_safe(void)
-{
-    alc_cleanup();
-
-    FreeHrtfs();
-    FreeALConfig();
-
-    almtx_destroy(&ListLock);
-    altss_delete(LocalContext);
-
-    if(LogFile != stderr)
-        fclose(LogFile);
-    LogFile = NULL;
-
-    althrd_deinit();
-}
-
-static void alc_deinit(void)
-{
-    int i;
-
-    alc_cleanup();
-
-    memset(&PlaybackBackend, 0, sizeof(PlaybackBackend));
-    memset(&CaptureBackend, 0, sizeof(CaptureBackend));
-
-    for(i = 0;i < BackendListSize;i++)
-    {
-        ALCbackendFactory *factory = BackendList[i].getFactory();
-        V0(factory,deinit)();
-    }
-    {
-        ALCbackendFactory *factory = ALCloopbackFactory_getFactory();
-        V0(factory,deinit)();
-    }
-
-    alc_deinit_safe();
-}
-
-
-/************************************************
- * Device enumeration
- ************************************************/
-static void ProbeDevices(al_string *list, struct BackendInfo *backendinfo, enum DevProbe type)
-{
-    DO_INITCONFIG();
-
-    LockLists();
-    alstr_clear(list);
-
-    if(backendinfo->getFactory)
-    {
-        ALCbackendFactory *factory = backendinfo->getFactory();
-        V(factory,probe)(type);
-    }
-
-    UnlockLists();
-}
-static void ProbeAllDevicesList(void)
-{ ProbeDevices(&alcAllDevicesList, &PlaybackBackend, ALL_DEVICE_PROBE); }
-static void ProbeCaptureDeviceList(void)
-{ ProbeDevices(&alcCaptureDeviceList, &CaptureBackend, CAPTURE_DEVICE_PROBE); }
-
-static void AppendDevice(const ALCchar *name, al_string *devnames)
-{
-    size_t len = strlen(name);
-    if(len > 0)
-        alstr_append_range(devnames, name, name+len+1);
-}
-void AppendAllDevicesList(const ALCchar *name)
-{ AppendDevice(name, &alcAllDevicesList); }
-void AppendCaptureDeviceList(const ALCchar *name)
-{ AppendDevice(name, &alcCaptureDeviceList); }
-
-
-/************************************************
- * Device format information
- ************************************************/
-const ALCchar *DevFmtTypeString(enum DevFmtType type)
-{
-    switch(type)
-    {
-    case DevFmtByte: return "Signed Byte";
-    case DevFmtUByte: return "Unsigned Byte";
-    case DevFmtShort: return "Signed Short";
-    case DevFmtUShort: return "Unsigned Short";
-    case DevFmtInt: return "Signed Int";
-    case DevFmtUInt: return "Unsigned Int";
-    case DevFmtFloat: return "Float";
-    }
-    return "(unknown type)";
-}
-const ALCchar *DevFmtChannelsString(enum DevFmtChannels chans)
-{
-    switch(chans)
-    {
-    case DevFmtMono: return "Mono";
-    case DevFmtStereo: return "Stereo";
-    case DevFmtQuad: return "Quadraphonic";
-    case DevFmtX51: return "5.1 Surround";
-    case DevFmtX51Rear: return "5.1 Surround (Rear)";
-    case DevFmtX61: return "6.1 Surround";
-    case DevFmtX71: return "7.1 Surround";
-    case DevFmtAmbi3D: return "Ambisonic 3D";
-    }
-    return "(unknown channels)";
-}
-
-extern inline ALsizei FrameSizeFromDevFmt(enum DevFmtChannels chans, enum DevFmtType type, ALsizei ambiorder);
-ALsizei BytesFromDevFmt(enum DevFmtType type)
-{
-    switch(type)
-    {
-    case DevFmtByte: return sizeof(ALbyte);
-    case DevFmtUByte: return sizeof(ALubyte);
-    case DevFmtShort: return sizeof(ALshort);
-    case DevFmtUShort: return sizeof(ALushort);
-    case DevFmtInt: return sizeof(ALint);
-    case DevFmtUInt: return sizeof(ALuint);
-    case DevFmtFloat: return sizeof(ALfloat);
-    }
-    return 0;
-}
-ALsizei ChannelsFromDevFmt(enum DevFmtChannels chans, ALsizei ambiorder)
-{
-    switch(chans)
-    {
-    case DevFmtMono: return 1;
-    case DevFmtStereo: return 2;
-    case DevFmtQuad: return 4;
-    case DevFmtX51: return 6;
-    case DevFmtX51Rear: return 6;
-    case DevFmtX61: return 7;
-    case DevFmtX71: return 8;
-    case DevFmtAmbi3D: return (ambiorder >= 3) ? 16 :
-                              (ambiorder == 2) ? 9 :
-                              (ambiorder == 1) ? 4 : 1;
-    }
-    return 0;
-}
-
-static ALboolean DecomposeDevFormat(ALenum format, enum DevFmtChannels *chans,
-                                    enum DevFmtType *type)
-{
-    static const struct {
-        ALenum format;
-        enum DevFmtChannels channels;
-        enum DevFmtType type;
-    } list[] = {
-        { AL_FORMAT_MONO8,        DevFmtMono, DevFmtUByte },
-        { AL_FORMAT_MONO16,       DevFmtMono, DevFmtShort },
-        { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat },
-
-        { AL_FORMAT_STEREO8,        DevFmtStereo, DevFmtUByte },
-        { AL_FORMAT_STEREO16,       DevFmtStereo, DevFmtShort },
-        { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat },
-
-        { AL_FORMAT_QUAD8,  DevFmtQuad, DevFmtUByte },
-        { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort },
-        { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat },
-
-        { AL_FORMAT_51CHN8,  DevFmtX51, DevFmtUByte },
-        { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort },
-        { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat },
-
-        { AL_FORMAT_61CHN8,  DevFmtX61, DevFmtUByte },
-        { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort },
-        { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat },
-
-        { AL_FORMAT_71CHN8,  DevFmtX71, DevFmtUByte },
-        { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort },
-        { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat },
-    };
-    ALuint i;
-
-    for(i = 0;i < COUNTOF(list);i++)
-    {
-        if(list[i].format == format)
-        {
-            *chans = list[i].channels;
-            *type  = list[i].type;
-            return AL_TRUE;
-        }
-    }
-
-    return AL_FALSE;
-}
-
-static ALCboolean IsValidALCType(ALCenum type)
-{
-    switch(type)
-    {
-        case ALC_BYTE_SOFT:
-        case ALC_UNSIGNED_BYTE_SOFT:
-        case ALC_SHORT_SOFT:
-        case ALC_UNSIGNED_SHORT_SOFT:
-        case ALC_INT_SOFT:
-        case ALC_UNSIGNED_INT_SOFT:
-        case ALC_FLOAT_SOFT:
-            return ALC_TRUE;
-    }
-    return ALC_FALSE;
-}
-
-static ALCboolean IsValidALCChannels(ALCenum channels)
-{
-    switch(channels)
-    {
-        case ALC_MONO_SOFT:
-        case ALC_STEREO_SOFT:
-        case ALC_QUAD_SOFT:
-        case ALC_5POINT1_SOFT:
-        case ALC_6POINT1_SOFT:
-        case ALC_7POINT1_SOFT:
-        case ALC_BFORMAT3D_SOFT:
-            return ALC_TRUE;
-    }
-    return ALC_FALSE;
-}
-
-static ALCboolean IsValidAmbiLayout(ALCenum layout)
-{
-    switch(layout)
-    {
-        case ALC_ACN_SOFT:
-        case ALC_FUMA_SOFT:
-            return ALC_TRUE;
-    }
-    return ALC_FALSE;
-}
-
-static ALCboolean IsValidAmbiScaling(ALCenum scaling)
-{
-    switch(scaling)
-    {
-        case ALC_N3D_SOFT:
-        case ALC_SN3D_SOFT:
-        case ALC_FUMA_SOFT:
-            return ALC_TRUE;
-    }
-    return ALC_FALSE;
-}
-
-/************************************************
- * Miscellaneous ALC helpers
- ************************************************/
-
-/* SetDefaultWFXChannelOrder
- *
- * Sets the default channel order used by WaveFormatEx.
- */
-void SetDefaultWFXChannelOrder(ALCdevice *device)
-{
-    ALsizei i;
-
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        device->RealOut.ChannelName[i] = InvalidChannel;
-
-    switch(device->FmtChans)
-    {
-    case DevFmtMono:
-        device->RealOut.ChannelName[0] = FrontCenter;
-        break;
-    case DevFmtStereo:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        break;
-    case DevFmtQuad:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = BackLeft;
-        device->RealOut.ChannelName[3] = BackRight;
-        break;
-    case DevFmtX51:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = FrontCenter;
-        device->RealOut.ChannelName[3] = LFE;
-        device->RealOut.ChannelName[4] = SideLeft;
-        device->RealOut.ChannelName[5] = SideRight;
-        break;
-    case DevFmtX51Rear:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = FrontCenter;
-        device->RealOut.ChannelName[3] = LFE;
-        device->RealOut.ChannelName[4] = BackLeft;
-        device->RealOut.ChannelName[5] = BackRight;
-        break;
-    case DevFmtX61:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = FrontCenter;
-        device->RealOut.ChannelName[3] = LFE;
-        device->RealOut.ChannelName[4] = BackCenter;
-        device->RealOut.ChannelName[5] = SideLeft;
-        device->RealOut.ChannelName[6] = SideRight;
-        break;
-    case DevFmtX71:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = FrontCenter;
-        device->RealOut.ChannelName[3] = LFE;
-        device->RealOut.ChannelName[4] = BackLeft;
-        device->RealOut.ChannelName[5] = BackRight;
-        device->RealOut.ChannelName[6] = SideLeft;
-        device->RealOut.ChannelName[7] = SideRight;
-        break;
-    case DevFmtAmbi3D:
-        device->RealOut.ChannelName[0] = Aux0;
-        if(device->AmbiOrder > 0)
-        {
-            device->RealOut.ChannelName[1] = Aux1;
-            device->RealOut.ChannelName[2] = Aux2;
-            device->RealOut.ChannelName[3] = Aux3;
-        }
-        if(device->AmbiOrder > 1)
-        {
-            device->RealOut.ChannelName[4] = Aux4;
-            device->RealOut.ChannelName[5] = Aux5;
-            device->RealOut.ChannelName[6] = Aux6;
-            device->RealOut.ChannelName[7] = Aux7;
-            device->RealOut.ChannelName[8] = Aux8;
-        }
-        if(device->AmbiOrder > 2)
-        {
-            device->RealOut.ChannelName[9]  = Aux9;
-            device->RealOut.ChannelName[10] = Aux10;
-            device->RealOut.ChannelName[11] = Aux11;
-            device->RealOut.ChannelName[12] = Aux12;
-            device->RealOut.ChannelName[13] = Aux13;
-            device->RealOut.ChannelName[14] = Aux14;
-            device->RealOut.ChannelName[15] = Aux15;
-        }
-        break;
-    }
-}
-
-/* SetDefaultChannelOrder
- *
- * Sets the default channel order used by most non-WaveFormatEx-based APIs.
- */
-void SetDefaultChannelOrder(ALCdevice *device)
-{
-    ALsizei i;
-
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        device->RealOut.ChannelName[i] = InvalidChannel;
-
-    switch(device->FmtChans)
-    {
-    case DevFmtX51Rear:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = BackLeft;
-        device->RealOut.ChannelName[3] = BackRight;
-        device->RealOut.ChannelName[4] = FrontCenter;
-        device->RealOut.ChannelName[5] = LFE;
-        return;
-    case DevFmtX71:
-        device->RealOut.ChannelName[0] = FrontLeft;
-        device->RealOut.ChannelName[1] = FrontRight;
-        device->RealOut.ChannelName[2] = BackLeft;
-        device->RealOut.ChannelName[3] = BackRight;
-        device->RealOut.ChannelName[4] = FrontCenter;
-        device->RealOut.ChannelName[5] = LFE;
-        device->RealOut.ChannelName[6] = SideLeft;
-        device->RealOut.ChannelName[7] = SideRight;
-        return;
-
-    /* Same as WFX order */
-    case DevFmtMono:
-    case DevFmtStereo:
-    case DevFmtQuad:
-    case DevFmtX51:
-    case DevFmtX61:
-    case DevFmtAmbi3D:
-        SetDefaultWFXChannelOrder(device);
-        break;
-    }
-}
-
-extern inline ALint GetChannelIndex(const enum Channel names[MAX_OUTPUT_CHANNELS], enum Channel chan);
-extern inline ALint GetChannelIdxByName(const RealMixParams *real, enum Channel chan);
-
-
-/* ALCcontext_DeferUpdates
- *
- * 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 ALCcontext_DeferUpdates(ALCcontext *context)
-{
-    ATOMIC_STORE_SEQ(&context->DeferUpdates, AL_TRUE);
-}
-
-/* ALCcontext_ProcessUpdates
- *
- * Resumes update processing after being deferred.
- */
-void ALCcontext_ProcessUpdates(ALCcontext *context)
-{
-    almtx_lock(&context->PropLock);
-    if(ATOMIC_EXCHANGE_SEQ(&context->DeferUpdates, AL_FALSE))
-    {
-        /* Tell the mixer to stop applying updates, then wait for any active
-         * updating to finish, before providing updates.
-         */
-        ATOMIC_STORE_SEQ(&context->HoldUpdates, AL_TRUE);
-        while((ATOMIC_LOAD(&context->UpdateCount, almemory_order_acquire)&1) != 0)
-            althrd_yield();
-
-        if(!ATOMIC_FLAG_TEST_AND_SET(&context->PropsClean, almemory_order_acq_rel))
-            UpdateContextProps(context);
-        if(!ATOMIC_FLAG_TEST_AND_SET(&context->Listener->PropsClean, almemory_order_acq_rel))
-            UpdateListenerProps(context);
-        UpdateAllEffectSlotProps(context);
-        UpdateAllSourceProps(context);
-
-        /* Now with all updates declared, let the mixer continue applying them
-         * so they all happen at once.
-         */
-        ATOMIC_STORE_SEQ(&context->HoldUpdates, AL_FALSE);
-    }
-    almtx_unlock(&context->PropLock);
-}
-
-
-/* alcSetError
- *
- * Stores the latest ALC device error
- */
-static void alcSetError(ALCdevice *device, ALCenum errorCode)
-{
-    WARN("Error generated on device %p, code 0x%04x\n", device, errorCode);
-    if(TrapALCError)
-    {
-#ifdef _WIN32
-        /* DebugBreak() will cause an exception if there is no debugger */
-        if(IsDebuggerPresent())
-            DebugBreak();
-#elif defined(SIGTRAP)
-        raise(SIGTRAP);
-#endif
-    }
-
-    if(device)
-        ATOMIC_STORE_SEQ(&device->LastError, errorCode);
-    else
-        ATOMIC_STORE_SEQ(&LastNullDeviceError, errorCode);
-}
-
-
-struct Compressor *CreateDeviceLimiter(const ALCdevice *device)
-{
-    return CompressorInit(0.0f, 0.0f, AL_FALSE, AL_TRUE, 0.0f, 0.0f, 0.5f, 2.0f,
-                          0.0f, -3.0f, 3.0f, device->Frequency);
-}
-
-/* UpdateClockBase
- *
- * Updates the device's base clock time with however many samples have been
- * done. This is used so frequency changes on the device don't cause the time
- * to jump forward or back. Must not be called while the device is running/
- * mixing.
- */
-static inline void UpdateClockBase(ALCdevice *device)
-{
-    IncrementRef(&device->MixCount);
-    device->ClockBase += device->SamplesDone * DEVICE_CLOCK_RES / device->Frequency;
-    device->SamplesDone = 0;
-    IncrementRef(&device->MixCount);
-}
-
-/* UpdateDeviceParams
- *
- * Updates device parameters according to the attribute list (caller is
- * responsible for holding the list lock).
- */
-static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList)
-{
-    enum HrtfRequestMode hrtf_userreq = Hrtf_Default;
-    enum HrtfRequestMode hrtf_appreq = Hrtf_Default;
-    ALCenum gainLimiter = device->Limiter ? ALC_TRUE : ALC_FALSE;
-    const ALsizei old_sends = device->NumAuxSends;
-    ALsizei new_sends = device->NumAuxSends;
-    enum DevFmtChannels oldChans;
-    enum DevFmtType oldType;
-    ALboolean update_failed;
-    ALCsizei hrtf_id = -1;
-    ALCcontext *context;
-    ALCuint oldFreq;
-    size_t size;
-    ALCsizei i;
-    int val;
-
-    // Check for attributes
-    if(device->Type == Loopback)
-    {
-        ALCsizei numMono, numStereo, numSends;
-        ALCenum alayout = AL_NONE;
-        ALCenum ascale = AL_NONE;
-        ALCenum schans = AL_NONE;
-        ALCenum stype = AL_NONE;
-        ALCsizei attrIdx = 0;
-        ALCsizei aorder = 0;
-        ALCuint freq = 0;
-
-        if(!attrList)
-        {
-            WARN("Missing attributes for loopback device\n");
-            return ALC_INVALID_VALUE;
-        }
-
-        numMono = device->NumMonoSources;
-        numStereo = device->NumStereoSources;
-        numSends = old_sends;
-
-#define TRACE_ATTR(a, v) TRACE("Loopback %s = %d\n", #a, v)
-        while(attrList[attrIdx])
-        {
-            switch(attrList[attrIdx])
-            {
-                case ALC_FORMAT_CHANNELS_SOFT:
-                    schans = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, schans);
-                    if(!IsValidALCChannels(schans))
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_FORMAT_TYPE_SOFT:
-                    stype = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, stype);
-                    if(!IsValidALCType(stype))
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_FREQUENCY:
-                    freq = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_FREQUENCY, freq);
-                    if(freq < MIN_OUTPUT_RATE)
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_AMBISONIC_LAYOUT_SOFT:
-                    alayout = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, alayout);
-                    if(!IsValidAmbiLayout(alayout))
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_AMBISONIC_SCALING_SOFT:
-                    ascale = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, ascale);
-                    if(!IsValidAmbiScaling(ascale))
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_AMBISONIC_ORDER_SOFT:
-                    aorder = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder);
-                    if(aorder < 1 || aorder > MAX_AMBI_ORDER)
-                        return ALC_INVALID_VALUE;
-                    break;
-
-                case ALC_MONO_SOURCES:
-                    numMono = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_MONO_SOURCES, numMono);
-                    numMono = maxi(numMono, 0);
-                    break;
-
-                case ALC_STEREO_SOURCES:
-                    numStereo = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_STEREO_SOURCES, numStereo);
-                    numStereo = maxi(numStereo, 0);
-                    break;
-
-                case ALC_MAX_AUXILIARY_SENDS:
-                    numSends = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends);
-                    numSends = clampi(numSends, 0, MAX_SENDS);
-                    break;
-
-                case ALC_HRTF_SOFT:
-                    TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]);
-                    if(attrList[attrIdx + 1] == ALC_FALSE)
-                        hrtf_appreq = Hrtf_Disable;
-                    else if(attrList[attrIdx + 1] == ALC_TRUE)
-                        hrtf_appreq = Hrtf_Enable;
-                    else
-                        hrtf_appreq = Hrtf_Default;
-                    break;
-
-                case ALC_HRTF_ID_SOFT:
-                    hrtf_id = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id);
-                    break;
-
-                case ALC_OUTPUT_LIMITER_SOFT:
-                    gainLimiter = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter);
-                    break;
-
-                default:
-                    TRACE("Loopback 0x%04X = %d (0x%x)\n", attrList[attrIdx],
-                          attrList[attrIdx + 1], attrList[attrIdx + 1]);
-                    break;
-            }
-
-            attrIdx += 2;
-        }
-#undef TRACE_ATTR
-
-        if(!schans || !stype || !freq)
-        {
-            WARN("Missing format for loopback device\n");
-            return ALC_INVALID_VALUE;
-        }
-        if(schans == ALC_BFORMAT3D_SOFT && (!alayout || !ascale || !aorder))
-        {
-            WARN("Missing ambisonic info for loopback device\n");
-            return ALC_INVALID_VALUE;
-        }
-
-        if((device->Flags&DEVICE_RUNNING))
-            V0(device->Backend,stop)();
-        device->Flags &= ~DEVICE_RUNNING;
-
-        UpdateClockBase(device);
-
-        device->Frequency = freq;
-        device->FmtChans = schans;
-        device->FmtType = stype;
-        if(schans == ALC_BFORMAT3D_SOFT)
-        {
-            device->AmbiOrder = aorder;
-            device->AmbiLayout = alayout;
-            device->AmbiScale = ascale;
-        }
-
-        if(numMono > INT_MAX-numStereo)
-            numMono = INT_MAX-numStereo;
-        numMono += numStereo;
-        if(ConfigValueInt(NULL, NULL, "sources", &numMono))
-        {
-            if(numMono <= 0)
-                numMono = 256;
-        }
-        else
-            numMono = maxi(numMono, 256);
-        numStereo = mini(numStereo, numMono);
-        numMono -= numStereo;
-        device->SourcesMax = numMono + numStereo;
-
-        device->NumMonoSources = numMono;
-        device->NumStereoSources = numStereo;
-
-        if(ConfigValueInt(NULL, NULL, "sends", &new_sends))
-            new_sends = mini(numSends, clampi(new_sends, 0, MAX_SENDS));
-        else
-            new_sends = numSends;
-    }
-    else if(attrList && attrList[0])
-    {
-        ALCsizei numMono, numStereo, numSends;
-        ALCsizei attrIdx = 0;
-        ALCuint freq;
-
-        /* If a context is already running on the device, stop playback so the
-         * device attributes can be updated. */
-        if((device->Flags&DEVICE_RUNNING))
-            V0(device->Backend,stop)();
-        device->Flags &= ~DEVICE_RUNNING;
-
-        UpdateClockBase(device);
-
-        freq = device->Frequency;
-        numMono = device->NumMonoSources;
-        numStereo = device->NumStereoSources;
-        numSends = old_sends;
-
-#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v)
-        while(attrList[attrIdx])
-        {
-            switch(attrList[attrIdx])
-            {
-                case ALC_FREQUENCY:
-                    freq = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_FREQUENCY, freq);
-                    device->Flags |= DEVICE_FREQUENCY_REQUEST;
-                    break;
-
-                case ALC_MONO_SOURCES:
-                    numMono = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_MONO_SOURCES, numMono);
-                    numMono = maxi(numMono, 0);
-                    break;
-
-                case ALC_STEREO_SOURCES:
-                    numStereo = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_STEREO_SOURCES, numStereo);
-                    numStereo = maxi(numStereo, 0);
-                    break;
-
-                case ALC_MAX_AUXILIARY_SENDS:
-                    numSends = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends);
-                    numSends = clampi(numSends, 0, MAX_SENDS);
-                    break;
-
-                case ALC_HRTF_SOFT:
-                    TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]);
-                    if(attrList[attrIdx + 1] == ALC_FALSE)
-                        hrtf_appreq = Hrtf_Disable;
-                    else if(attrList[attrIdx + 1] == ALC_TRUE)
-                        hrtf_appreq = Hrtf_Enable;
-                    else
-                        hrtf_appreq = Hrtf_Default;
-                    break;
-
-                case ALC_HRTF_ID_SOFT:
-                    hrtf_id = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id);
-                    break;
-
-                case ALC_OUTPUT_LIMITER_SOFT:
-                    gainLimiter = attrList[attrIdx + 1];
-                    TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter);
-                    break;
-
-                default:
-                    TRACE("0x%04X = %d (0x%x)\n", attrList[attrIdx],
-                          attrList[attrIdx + 1], attrList[attrIdx + 1]);
-                    break;
-            }
-
-            attrIdx += 2;
-        }
-#undef TRACE_ATTR
-
-        ConfigValueUInt(alstr_get_cstr(device->DeviceName), NULL, "frequency", &freq);
-        freq = maxu(freq, MIN_OUTPUT_RATE);
-
-        device->UpdateSize = (ALuint64)device->UpdateSize * freq /
-                             device->Frequency;
-        /* SSE and Neon do best with the update size being a multiple of 4 */
-        if((CPUCapFlags&(CPU_CAP_SSE|CPU_CAP_NEON)) != 0)
-            device->UpdateSize = (device->UpdateSize+3)&~3;
-
-        device->Frequency = freq;
-
-        if(numMono > INT_MAX-numStereo)
-            numMono = INT_MAX-numStereo;
-        numMono += numStereo;
-        if(ConfigValueInt(alstr_get_cstr(device->DeviceName), NULL, "sources", &numMono))
-        {
-            if(numMono <= 0)
-                numMono = 256;
-        }
-        else
-            numMono = maxi(numMono, 256);
-        numStereo = mini(numStereo, numMono);
-        numMono -= numStereo;
-        device->SourcesMax = numMono + numStereo;
-
-        device->NumMonoSources = numMono;
-        device->NumStereoSources = numStereo;
-
-        if(ConfigValueInt(alstr_get_cstr(device->DeviceName), NULL, "sends", &new_sends))
-            new_sends = mini(numSends, clampi(new_sends, 0, MAX_SENDS));
-        else
-            new_sends = numSends;
-    }
-
-    if((device->Flags&DEVICE_RUNNING))
-        return ALC_NO_ERROR;
-
-    al_free(device->Uhj_Encoder);
-    device->Uhj_Encoder = NULL;
-
-    al_free(device->Bs2b);
-    device->Bs2b = NULL;
-
-    al_free(device->ChannelDelay[0].Buffer);
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        device->ChannelDelay[i].Length = 0;
-        device->ChannelDelay[i].Buffer = NULL;
-    }
-
-    al_free(device->Dry.Buffer);
-    device->Dry.Buffer = NULL;
-    device->Dry.NumChannels = 0;
-    device->FOAOut.Buffer = NULL;
-    device->FOAOut.NumChannels = 0;
-    device->RealOut.Buffer = NULL;
-    device->RealOut.NumChannels = 0;
-
-    UpdateClockBase(device);
-
-    device->DitherSeed = DITHER_RNG_SEED;
-
-    /*************************************************************************
-     * Update device format request if HRTF is requested
-     */
-    device->HrtfStatus = ALC_HRTF_DISABLED_SOFT;
-    if(device->Type != Loopback)
-    {
-        const char *hrtf;
-        if(ConfigValueStr(alstr_get_cstr(device->DeviceName), NULL, "hrtf", &hrtf))
-        {
-            if(strcasecmp(hrtf, "true") == 0)
-                hrtf_userreq = Hrtf_Enable;
-            else if(strcasecmp(hrtf, "false") == 0)
-                hrtf_userreq = Hrtf_Disable;
-            else if(strcasecmp(hrtf, "auto") != 0)
-                ERR("Unexpected hrtf value: %s\n", hrtf);
-        }
-
-        if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable))
-        {
-            struct Hrtf *hrtf = NULL;
-            if(VECTOR_SIZE(device->HrtfList) == 0)
-            {
-                VECTOR_DEINIT(device->HrtfList);
-                device->HrtfList = EnumerateHrtf(device->DeviceName);
-            }
-            if(VECTOR_SIZE(device->HrtfList) > 0)
-            {
-                if(hrtf_id >= 0 && (size_t)hrtf_id < VECTOR_SIZE(device->HrtfList))
-                    hrtf = GetLoadedHrtf(VECTOR_ELEM(device->HrtfList, hrtf_id).hrtf);
-                else
-                    hrtf = GetLoadedHrtf(VECTOR_ELEM(device->HrtfList, 0).hrtf);
-            }
-
-            if(hrtf)
-            {
-                device->FmtChans = DevFmtStereo;
-                device->Frequency = hrtf->sampleRate;
-                device->Flags |= DEVICE_CHANNELS_REQUEST | DEVICE_FREQUENCY_REQUEST;
-                if(device->HrtfHandle)
-                    Hrtf_DecRef(device->HrtfHandle);
-                device->HrtfHandle = hrtf;
-            }
-            else
-            {
-                hrtf_userreq = Hrtf_Default;
-                hrtf_appreq = Hrtf_Disable;
-                device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
-            }
-        }
-    }
-
-    oldFreq  = device->Frequency;
-    oldChans = device->FmtChans;
-    oldType  = device->FmtType;
-
-    TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u update size x%d\n",
-        (device->Flags&DEVICE_CHANNELS_REQUEST)?"*":"", DevFmtChannelsString(device->FmtChans),
-        (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST)?"*":"", DevFmtTypeString(device->FmtType),
-        (device->Flags&DEVICE_FREQUENCY_REQUEST)?"*":"", device->Frequency,
-        device->UpdateSize, device->NumUpdates
-    );
-
-    if(V0(device->Backend,reset)() == ALC_FALSE)
-        return ALC_INVALID_DEVICE;
-
-    if(device->FmtChans != oldChans && (device->Flags&DEVICE_CHANNELS_REQUEST))
-    {
-        ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans),
-            DevFmtChannelsString(device->FmtChans));
-        device->Flags &= ~DEVICE_CHANNELS_REQUEST;
-    }
-    if(device->FmtType != oldType && (device->Flags&DEVICE_SAMPLE_TYPE_REQUEST))
-    {
-        ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType),
-            DevFmtTypeString(device->FmtType));
-        device->Flags &= ~DEVICE_SAMPLE_TYPE_REQUEST;
-    }
-    if(device->Frequency != oldFreq && (device->Flags&DEVICE_FREQUENCY_REQUEST))
-    {
-        ERR("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency);
-        device->Flags &= ~DEVICE_FREQUENCY_REQUEST;
-    }
-
-    if((device->UpdateSize&3) != 0)
-    {
-        if((CPUCapFlags&CPU_CAP_SSE))
-            WARN("SSE performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize);
-        if((CPUCapFlags&CPU_CAP_NEON))
-            WARN("NEON performs best with multiple of 4 update sizes (%u)\n", device->UpdateSize);
-    }
-
-    TRACE("Post-reset: %s, %s, %uhz, %u update size x%d\n",
-        DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
-        device->Frequency, device->UpdateSize, device->NumUpdates
-    );
-
-    aluInitRenderer(device, hrtf_id, hrtf_appreq, hrtf_userreq);
-    TRACE("Channel config, Dry: %d, FOA: %d, Real: %d\n", device->Dry.NumChannels,
-          device->FOAOut.NumChannels, device->RealOut.NumChannels);
-
-    /* Allocate extra channels for any post-filter output. */
-    size = (device->Dry.NumChannels + device->FOAOut.NumChannels +
-            device->RealOut.NumChannels)*sizeof(device->Dry.Buffer[0]);
-
-    TRACE("Allocating "SZFMT" channels, "SZFMT" bytes\n", size/sizeof(device->Dry.Buffer[0]), size);
-    device->Dry.Buffer = al_calloc(16, size);
-    if(!device->Dry.Buffer)
-    {
-        ERR("Failed to allocate "SZFMT" bytes for mix buffer\n", size);
-        return ALC_INVALID_DEVICE;
-    }
-
-    if(device->RealOut.NumChannels != 0)
-        device->RealOut.Buffer = device->Dry.Buffer + device->Dry.NumChannels +
-                                 device->FOAOut.NumChannels;
-    else
-    {
-        device->RealOut.Buffer = device->Dry.Buffer;
-        device->RealOut.NumChannels = device->Dry.NumChannels;
-    }
-
-    if(device->FOAOut.NumChannels != 0)
-        device->FOAOut.Buffer = device->Dry.Buffer + device->Dry.NumChannels;
-    else
-    {
-        device->FOAOut.Buffer = device->Dry.Buffer;
-        device->FOAOut.NumChannels = device->Dry.NumChannels;
-    }
-
-    device->NumAuxSends = new_sends;
-    TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n",
-          device->SourcesMax, device->NumMonoSources, device->NumStereoSources,
-          device->AuxiliaryEffectSlotMax, device->NumAuxSends);
-
-    device->DitherDepth = 0.0f;
-    if(GetConfigValueBool(alstr_get_cstr(device->DeviceName), NULL, "dither", 1))
-    {
-        ALint depth = 0;
-        ConfigValueInt(alstr_get_cstr(device->DeviceName), NULL, "dither-depth", &depth);
-        if(depth <= 0)
-        {
-            switch(device->FmtType)
-            {
-                case DevFmtByte:
-                case DevFmtUByte:
-                    depth = 8;
-                    break;
-                case DevFmtShort:
-                case DevFmtUShort:
-                    depth = 16;
-                    break;
-                case DevFmtInt:
-                case DevFmtUInt:
-                case DevFmtFloat:
-                    break;
-            }
-        }
-        else if(depth > 24)
-            depth = 24;
-        device->DitherDepth = (depth > 0) ? powf(2.0f, (ALfloat)(depth-1)) : 0.0f;
-    }
-    if(!(device->DitherDepth > 0.0f))
-        TRACE("Dithering disabled\n");
-    else
-        TRACE("Dithering enabled (%g-bit, %g)\n", log2f(device->DitherDepth)+1.0f,
-              device->DitherDepth);
-
-    if(ConfigValueBool(alstr_get_cstr(device->DeviceName), NULL, "output-limiter", &val))
-        gainLimiter = val ? ALC_TRUE : ALC_FALSE;
-    /* Valid values for gainLimiter are ALC_DONT_CARE_SOFT, ALC_TRUE, and
-     * ALC_FALSE. We default to on, so ALC_DONT_CARE_SOFT is the same as
-     * ALC_TRUE.
-     */
-    if(gainLimiter != ALC_FALSE)
-    {
-        if(!device->Limiter || device->Frequency != GetCompressorSampleRate(device->Limiter))
-        {
-            al_free(device->Limiter);
-            device->Limiter = CreateDeviceLimiter(device);
-        }
-    }
-    else
-    {
-        al_free(device->Limiter);
-        device->Limiter = NULL;
-    }
-    TRACE("Output limiter %s\n", device->Limiter ? "enabled" : "disabled");
-
-    aluSelectPostProcess(device);
-
-    /* Need to delay returning failure until replacement Send arrays have been
-     * allocated with the appropriate size.
-     */
-    update_failed = AL_FALSE;
-    START_MIXER_MODE();
-    context = ATOMIC_LOAD_SEQ(&device->ContextList);
-    while(context)
-    {
-        SourceSubList *sublist, *subend;
-        struct ALvoiceProps *vprops;
-        ALsizei pos;
-
-        if(context->DefaultSlot)
-        {
-            ALeffectslot *slot = context->DefaultSlot;
-            ALeffectState *state = slot->Effect.State;
-
-            state->OutBuffer = device->Dry.Buffer;
-            state->OutChannels = device->Dry.NumChannels;
-            if(V(state,deviceUpdate)(device) == AL_FALSE)
-                update_failed = AL_TRUE;
-            else
-                UpdateEffectSlotProps(slot, context);
-        }
-
-        almtx_lock(&context->PropLock);
-        almtx_lock(&context->EffectSlotLock);
-        for(pos = 0;pos < (ALsizei)VECTOR_SIZE(context->EffectSlotList);pos++)
-        {
-            ALeffectslot *slot = VECTOR_ELEM(context->EffectSlotList, pos);
-            ALeffectState *state = slot->Effect.State;
-
-            state->OutBuffer = device->Dry.Buffer;
-            state->OutChannels = device->Dry.NumChannels;
-            if(V(state,deviceUpdate)(device) == AL_FALSE)
-                update_failed = AL_TRUE;
-            else
-                UpdateEffectSlotProps(slot, context);
-        }
-        almtx_unlock(&context->EffectSlotLock);
-
-        almtx_lock(&context->SourceLock);
-        sublist = VECTOR_BEGIN(context->SourceList);
-        subend = VECTOR_END(context->SourceList);
-        for(;sublist != subend;++sublist)
-        {
-            ALuint64 usemask = ~sublist->FreeMask;
-            while(usemask)
-            {
-                ALsizei idx = CTZ64(usemask);
-                ALsource *source = sublist->Sources + idx;
-
-                usemask &= ~(U64(1) << idx);
-
-                if(old_sends != device->NumAuxSends)
-                {
-                    ALvoid *sends = al_calloc(16, device->NumAuxSends*sizeof(source->Send[0]));
-                    ALsizei s;
-
-                    memcpy(sends, source->Send,
-                        mini(device->NumAuxSends, old_sends)*sizeof(source->Send[0])
-                    );
-                    for(s = device->NumAuxSends;s < old_sends;s++)
-                    {
-                        if(source->Send[s].Slot)
-                            DecrementRef(&source->Send[s].Slot->ref);
-                        source->Send[s].Slot = NULL;
-                    }
-                    al_free(source->Send);
-                    source->Send = sends;
-                    for(s = old_sends;s < device->NumAuxSends;s++)
-                    {
-                        source->Send[s].Slot = NULL;
-                        source->Send[s].Gain = 1.0f;
-                        source->Send[s].GainHF = 1.0f;
-                        source->Send[s].HFReference = LOWPASSFREQREF;
-                        source->Send[s].GainLF = 1.0f;
-                        source->Send[s].LFReference = HIGHPASSFREQREF;
-                    }
-                }
-
-                ATOMIC_FLAG_CLEAR(&source->PropsClean, almemory_order_release);
-            }
-        }
-
-        /* Clear any pre-existing voice property structs, in case the number of
-         * auxiliary sends is changing. Active sources will have updates
-         * respecified in UpdateAllSourceProps.
-         */
-        vprops = ATOMIC_EXCHANGE_PTR(&context->FreeVoiceProps, NULL, almemory_order_acq_rel);
-        while(vprops)
-        {
-            struct ALvoiceProps *next = ATOMIC_LOAD(&vprops->next, almemory_order_relaxed);
-            al_free(vprops);
-            vprops = next;
-        }
-
-        AllocateVoices(context, context->MaxVoices, old_sends);
-        for(pos = 0;pos < context->VoiceCount;pos++)
-        {
-            ALvoice *voice = context->Voices[pos];
-
-            al_free(ATOMIC_EXCHANGE_PTR(&voice->Update, NULL, almemory_order_acq_rel));
-
-            if(ATOMIC_LOAD(&voice->Source, almemory_order_acquire) == NULL)
-                continue;
-
-            if(device->AvgSpeakerDist > 0.0f)
-            {
-                /* Reinitialize the NFC filters for new parameters. */
-                ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
-                             (device->AvgSpeakerDist * device->Frequency);
-                for(i = 0;i < voice->NumChannels;i++)
-                    NfcFilterCreate(&voice->Direct.Params[i].NFCtrlFilter, 0.0f, w1);
-            }
-        }
-        almtx_unlock(&context->SourceLock);
-
-        ATOMIC_FLAG_TEST_AND_SET(&context->PropsClean, almemory_order_release);
-        UpdateContextProps(context);
-        ATOMIC_FLAG_TEST_AND_SET(&context->Listener->PropsClean, almemory_order_release);
-        UpdateListenerProps(context);
-        UpdateAllSourceProps(context);
-        almtx_unlock(&context->PropLock);
-
-        context = ATOMIC_LOAD(&context->next, almemory_order_relaxed);
-    }
-    END_MIXER_MODE();
-    if(update_failed)
-        return ALC_INVALID_DEVICE;
-
-    if(!(device->Flags&DEVICE_PAUSED))
-    {
-        if(V0(device->Backend,start)() == ALC_FALSE)
-            return ALC_INVALID_DEVICE;
-        device->Flags |= DEVICE_RUNNING;
-    }
-
-    return ALC_NO_ERROR;
-}
-
-
-static void InitDevice(ALCdevice *device, enum DeviceType type)
-{
-    ALsizei i;
-
-    InitRef(&device->ref, 1);
-    ATOMIC_INIT(&device->Connected, ALC_TRUE);
-    device->Type = type;
-    ATOMIC_INIT(&device->LastError, ALC_NO_ERROR);
-
-    device->Flags = 0;
-    device->Render_Mode = NormalRender;
-    device->AvgSpeakerDist = 0.0f;
-
-    ATOMIC_INIT(&device->ContextList, NULL);
-
-    device->ClockBase = 0;
-    device->SamplesDone = 0;
-
-    device->SourcesMax = 0;
-    device->AuxiliaryEffectSlotMax = 0;
-    device->NumAuxSends = 0;
-
-    device->Dry.Buffer = NULL;
-    device->Dry.NumChannels = 0;
-    device->FOAOut.Buffer = NULL;
-    device->FOAOut.NumChannels = 0;
-    device->RealOut.Buffer = NULL;
-    device->RealOut.NumChannels = 0;
-
-    AL_STRING_INIT(device->DeviceName);
-
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        device->ChannelDelay[i].Gain   = 1.0f;
-        device->ChannelDelay[i].Length = 0;
-        device->ChannelDelay[i].Buffer = NULL;
-    }
-
-    AL_STRING_INIT(device->HrtfName);
-    VECTOR_INIT(device->HrtfList);
-    device->HrtfHandle = NULL;
-    device->Hrtf = NULL;
-    device->Bs2b = NULL;
-    device->Uhj_Encoder = NULL;
-    device->AmbiDecoder = NULL;
-    device->AmbiUp = NULL;
-    device->Stablizer = NULL;
-    device->Limiter = NULL;
-
-    VECTOR_INIT(device->BufferList);
-    almtx_init(&device->BufferLock, almtx_plain);
-
-    VECTOR_INIT(device->EffectList);
-    almtx_init(&device->EffectLock, almtx_plain);
-
-    VECTOR_INIT(device->FilterList);
-    almtx_init(&device->FilterLock, almtx_plain);
-
-    almtx_init(&device->BackendLock, almtx_plain);
-    device->Backend = NULL;
-
-    ATOMIC_INIT(&device->next, NULL);
-}
-
-/* FreeDevice
- *
- * Frees the device structure, and destroys any objects the app failed to
- * delete. Called once there's no more references on the device.
- */
-static ALCvoid FreeDevice(ALCdevice *device)
-{
-    ALsizei i;
-
-    TRACE("%p\n", device);
-
-    if(device->Backend)
-        DELETE_OBJ(device->Backend);
-    device->Backend = NULL;
-
-    almtx_destroy(&device->BackendLock);
-
-    ReleaseALBuffers(device);
-#define FREE_BUFFERSUBLIST(x) al_free((x)->Buffers)
-    VECTOR_FOR_EACH(BufferSubList, device->BufferList, FREE_BUFFERSUBLIST);
-#undef FREE_BUFFERSUBLIST
-    VECTOR_DEINIT(device->BufferList);
-    almtx_destroy(&device->BufferLock);
-
-    ReleaseALEffects(device);
-#define FREE_EFFECTSUBLIST(x) al_free((x)->Effects)
-    VECTOR_FOR_EACH(EffectSubList, device->EffectList, FREE_EFFECTSUBLIST);
-#undef FREE_EFFECTSUBLIST
-    VECTOR_DEINIT(device->EffectList);
-    almtx_destroy(&device->EffectLock);
-
-    ReleaseALFilters(device);
-#define FREE_FILTERSUBLIST(x) al_free((x)->Filters)
-    VECTOR_FOR_EACH(FilterSubList, device->FilterList, FREE_FILTERSUBLIST);
-#undef FREE_FILTERSUBLIST
-    VECTOR_DEINIT(device->FilterList);
-    almtx_destroy(&device->FilterLock);
-
-    AL_STRING_DEINIT(device->HrtfName);
-    FreeHrtfList(&device->HrtfList);
-    if(device->HrtfHandle)
-        Hrtf_DecRef(device->HrtfHandle);
-    device->HrtfHandle = NULL;
-    al_free(device->Hrtf);
-    device->Hrtf = NULL;
-
-    al_free(device->Bs2b);
-    device->Bs2b = NULL;
-
-    al_free(device->Uhj_Encoder);
-    device->Uhj_Encoder = NULL;
-
-    bformatdec_free(&device->AmbiDecoder);
-    ambiup_free(&device->AmbiUp);
-
-    al_free(device->Stablizer);
-    device->Stablizer = NULL;
-
-    al_free(device->Limiter);
-    device->Limiter = NULL;
-
-    al_free(device->ChannelDelay[0].Buffer);
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        device->ChannelDelay[i].Gain   = 1.0f;
-        device->ChannelDelay[i].Length = 0;
-        device->ChannelDelay[i].Buffer = NULL;
-    }
-
-    AL_STRING_DEINIT(device->DeviceName);
-
-    al_free(device->Dry.Buffer);
-    device->Dry.Buffer = NULL;
-    device->Dry.NumChannels = 0;
-    device->FOAOut.Buffer = NULL;
-    device->FOAOut.NumChannels = 0;
-    device->RealOut.Buffer = NULL;
-    device->RealOut.NumChannels = 0;
-
-    al_free(device);
-}
-
-
-void ALCdevice_IncRef(ALCdevice *device)
-{
-    uint ref;
-    ref = IncrementRef(&device->ref);
-    TRACEREF("%p increasing refcount to %u\n", device, ref);
-}
-
-void ALCdevice_DecRef(ALCdevice *device)
-{
-    uint ref;
-    ref = DecrementRef(&device->ref);
-    TRACEREF("%p decreasing refcount to %u\n", device, ref);
-    if(ref == 0) FreeDevice(device);
-}
-
-/* VerifyDevice
- *
- * Checks if the device handle is valid, and increments its ref count if so.
- */
-static ALCboolean VerifyDevice(ALCdevice **device)
-{
-    ALCdevice *tmpDevice;
-
-    LockLists();
-    tmpDevice = ATOMIC_LOAD_SEQ(&DeviceList);
-    while(tmpDevice)
-    {
-        if(tmpDevice == *device)
-        {
-            ALCdevice_IncRef(tmpDevice);
-            UnlockLists();
-            return ALC_TRUE;
-        }
-        tmpDevice = ATOMIC_LOAD(&tmpDevice->next, almemory_order_relaxed);
-    }
-    UnlockLists();
-
-    *device = NULL;
-    return ALC_FALSE;
-}
-
-
-/* InitContext
- *
- * Initializes context fields
- */
-static ALvoid InitContext(ALCcontext *Context)
-{
-    ALlistener *listener = Context->Listener;
-    struct ALeffectslotArray *auxslots;
-
-    //Initialise listener
-    listener->Gain = 1.0f;
-    listener->Position[0] = 0.0f;
-    listener->Position[1] = 0.0f;
-    listener->Position[2] = 0.0f;
-    listener->Velocity[0] = 0.0f;
-    listener->Velocity[1] = 0.0f;
-    listener->Velocity[2] = 0.0f;
-    listener->Forward[0] = 0.0f;
-    listener->Forward[1] = 0.0f;
-    listener->Forward[2] = -1.0f;
-    listener->Up[0] = 0.0f;
-    listener->Up[1] = 1.0f;
-    listener->Up[2] = 0.0f;
-    ATOMIC_FLAG_TEST_AND_SET(&listener->PropsClean, almemory_order_relaxed);
-
-    ATOMIC_INIT(&listener->Update, NULL);
-
-    //Validate Context
-    InitRef(&Context->UpdateCount, 0);
-    ATOMIC_INIT(&Context->HoldUpdates, AL_FALSE);
-    Context->GainBoost = 1.0f;
-    almtx_init(&Context->PropLock, almtx_plain);
-    ATOMIC_INIT(&Context->LastError, AL_NO_ERROR);
-    VECTOR_INIT(Context->SourceList);
-    Context->NumSources = 0;
-    almtx_init(&Context->SourceLock, almtx_plain);
-    VECTOR_INIT(Context->EffectSlotList);
-    almtx_init(&Context->EffectSlotLock, almtx_plain);
-
-    if(Context->DefaultSlot)
-    {
-        auxslots = al_calloc(DEF_ALIGN, FAM_SIZE(struct ALeffectslotArray, slot, 1));
-        auxslots->count = 1;
-        auxslots->slot[0] = Context->DefaultSlot;
-    }
-    else
-    {
-        auxslots = al_calloc(DEF_ALIGN, sizeof(struct ALeffectslotArray));
-        auxslots->count = 0;
-    }
-    ATOMIC_INIT(&Context->ActiveAuxSlots, auxslots);
-
-    //Set globals
-    Context->DistanceModel = DefaultDistanceModel;
-    Context->SourceDistanceModel = AL_FALSE;
-    Context->DopplerFactor = 1.0f;
-    Context->DopplerVelocity = 1.0f;
-    Context->SpeedOfSound = SPEEDOFSOUNDMETRESPERSEC;
-    Context->MetersPerUnit = AL_DEFAULT_METERS_PER_UNIT;
-    ATOMIC_FLAG_TEST_AND_SET(&Context->PropsClean, almemory_order_relaxed);
-    ATOMIC_INIT(&Context->DeferUpdates, AL_FALSE);
-    almtx_init(&Context->EventThrdLock, almtx_plain);
-    alsem_init(&Context->EventSem, 0);
-    Context->AsyncEvents = NULL;
-    ATOMIC_INIT(&Context->EnabledEvts, 0);
-    almtx_init(&Context->EventCbLock, almtx_plain);
-    Context->EventCb = NULL;
-    Context->EventParam = NULL;
-
-    ATOMIC_INIT(&Context->Update, NULL);
-    ATOMIC_INIT(&Context->FreeContextProps, NULL);
-    ATOMIC_INIT(&Context->FreeListenerProps, NULL);
-    ATOMIC_INIT(&Context->FreeVoiceProps, NULL);
-    ATOMIC_INIT(&Context->FreeEffectslotProps, NULL);
-
-    Context->ExtensionList = alExtList;
-
-
-    listener->Params.Matrix = IdentityMatrixf;
-    aluVectorSet(&listener->Params.Velocity, 0.0f, 0.0f, 0.0f, 0.0f);
-    listener->Params.Gain = listener->Gain;
-    listener->Params.MetersPerUnit = Context->MetersPerUnit;
-    listener->Params.DopplerFactor = Context->DopplerFactor;
-    listener->Params.SpeedOfSound = Context->SpeedOfSound * Context->DopplerVelocity;
-    listener->Params.ReverbSpeedOfSound = listener->Params.SpeedOfSound *
-                                          listener->Params.MetersPerUnit;
-    listener->Params.SourceDistanceModel = Context->SourceDistanceModel;
-    listener->Params.DistanceModel = Context->DistanceModel;
-}
-
-
-/* FreeContext
- *
- * Cleans up the context, and destroys any remaining objects the app failed to
- * delete. Called once there's no more references on the context.
- */
-static void FreeContext(ALCcontext *context)
-{
-    ALlistener *listener = context->Listener;
-    struct ALeffectslotArray *auxslots;
-    struct ALeffectslotProps *eprops;
-    struct ALlistenerProps *lprops;
-    struct ALcontextProps *cprops;
-    struct ALvoiceProps *vprops;
-    size_t count;
-    ALsizei i;
-
-    TRACE("%p\n", context);
-
-    if((cprops=ATOMIC_LOAD(&context->Update, almemory_order_acquire)) != NULL)
-    {
-        TRACE("Freed unapplied context update %p\n", cprops);
-        al_free(cprops);
-    }
-
-    count = 0;
-    cprops = ATOMIC_LOAD(&context->FreeContextProps, almemory_order_acquire);
-    while(cprops)
-    {
-        struct ALcontextProps *next = ATOMIC_LOAD(&cprops->next, almemory_order_acquire);
-        al_free(cprops);
-        cprops = next;
-        ++count;
-    }
-    TRACE("Freed "SZFMT" context property object%s\n", count, (count==1)?"":"s");
-
-    if(context->DefaultSlot)
-    {
-        DeinitEffectSlot(context->DefaultSlot);
-        context->DefaultSlot = NULL;
-    }
-
-    auxslots = ATOMIC_EXCHANGE_PTR(&context->ActiveAuxSlots, NULL, almemory_order_relaxed);
-    al_free(auxslots);
-
-    ReleaseALSources(context);
-#define FREE_SOURCESUBLIST(x) al_free((x)->Sources)
-    VECTOR_FOR_EACH(SourceSubList, context->SourceList, FREE_SOURCESUBLIST);
-#undef FREE_SOURCESUBLIST
-    VECTOR_DEINIT(context->SourceList);
-    context->NumSources = 0;
-    almtx_destroy(&context->SourceLock);
-
-    count = 0;
-    eprops = ATOMIC_LOAD(&context->FreeEffectslotProps, almemory_order_relaxed);
-    while(eprops)
-    {
-        struct ALeffectslotProps *next = ATOMIC_LOAD(&eprops->next, almemory_order_relaxed);
-        if(eprops->State) ALeffectState_DecRef(eprops->State);
-        al_free(eprops);
-        eprops = next;
-        ++count;
-    }
-    TRACE("Freed "SZFMT" AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
-
-    ReleaseALAuxiliaryEffectSlots(context);
-#define FREE_EFFECTSLOTPTR(x) al_free(*(x))
-    VECTOR_FOR_EACH(ALeffectslotPtr, context->EffectSlotList, FREE_EFFECTSLOTPTR);
-#undef FREE_EFFECTSLOTPTR
-    VECTOR_DEINIT(context->EffectSlotList);
-    almtx_destroy(&context->EffectSlotLock);
-
-    count = 0;
-    vprops = ATOMIC_LOAD(&context->FreeVoiceProps, almemory_order_relaxed);
-    while(vprops)
-    {
-        struct ALvoiceProps *next = ATOMIC_LOAD(&vprops->next, almemory_order_relaxed);
-        al_free(vprops);
-        vprops = next;
-        ++count;
-    }
-    TRACE("Freed "SZFMT" voice property object%s\n", count, (count==1)?"":"s");
-
-    for(i = 0;i < context->VoiceCount;i++)
-        DeinitVoice(context->Voices[i]);
-    al_free(context->Voices);
-    context->Voices = NULL;
-    context->VoiceCount = 0;
-    context->MaxVoices = 0;
-
-    if((lprops=ATOMIC_LOAD(&listener->Update, almemory_order_acquire)) != NULL)
-    {
-        TRACE("Freed unapplied listener update %p\n", lprops);
-        al_free(lprops);
-    }
-    count = 0;
-    lprops = ATOMIC_LOAD(&context->FreeListenerProps, almemory_order_acquire);
-    while(lprops)
-    {
-        struct ALlistenerProps *next = ATOMIC_LOAD(&lprops->next, almemory_order_acquire);
-        al_free(lprops);
-        lprops = next;
-        ++count;
-    }
-    TRACE("Freed "SZFMT" listener property object%s\n", count, (count==1)?"":"s");
-
-    if(ATOMIC_EXCHANGE(&context->EnabledEvts, 0, almemory_order_acq_rel))
-    {
-        static const AsyncEvent kill_evt = { 0 };
-        while(ll_ringbuffer_write(context->AsyncEvents, (const char*)&kill_evt, 1) == 0)
-            althrd_yield();
-        alsem_post(&context->EventSem);
-        althrd_join(context->EventThread, NULL);
-    }
-
-    almtx_destroy(&context->EventCbLock);
-    almtx_destroy(&context->EventThrdLock);
-    alsem_destroy(&context->EventSem);
-
-    ll_ringbuffer_free(context->AsyncEvents);
-    context->AsyncEvents = NULL;
-
-    almtx_destroy(&context->PropLock);
-
-    ALCdevice_DecRef(context->Device);
-    context->Device = NULL;
-
-    //Invalidate context
-    memset(context, 0, sizeof(ALCcontext));
-    al_free(context);
-}
-
-/* ReleaseContext
- *
- * Removes the context reference from the given device and removes it from
- * being current on the running thread or globally. Returns true if other
- * contexts still exist on the device.
- */
-static bool ReleaseContext(ALCcontext *context, ALCdevice *device)
-{
-    ALCcontext *origctx, *newhead;
-    bool ret = true;
-
-    if(altss_get(LocalContext) == context)
-    {
-        WARN("%p released while current on thread\n", context);
-        altss_set(LocalContext, NULL);
-        ALCcontext_DecRef(context);
-    }
-
-    origctx = context;
-    if(ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&GlobalContext, &origctx, NULL))
-        ALCcontext_DecRef(context);
-
-    V0(device->Backend,lock)();
-    origctx = context;
-    newhead = ATOMIC_LOAD(&context->next, almemory_order_relaxed);
-    if(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&device->ContextList, &origctx, newhead))
-    {
-        ALCcontext *list;
-        do {
-            /* origctx is what the desired context failed to match. Try
-             * swapping out the next one in the list.
-             */
-            list = origctx;
-            origctx = context;
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&list->next, &origctx, newhead));
-    }
-    else
-        ret = !!newhead;
-    V0(device->Backend,unlock)();
-
-    ALCcontext_DecRef(context);
-    return ret;
-}
-
-static void ALCcontext_IncRef(ALCcontext *context)
-{
-    uint ref = IncrementRef(&context->ref);
-    TRACEREF("%p increasing refcount to %u\n", context, ref);
-}
-
-void ALCcontext_DecRef(ALCcontext *context)
-{
-    uint ref = DecrementRef(&context->ref);
-    TRACEREF("%p decreasing refcount to %u\n", context, ref);
-    if(ref == 0) FreeContext(context);
-}
-
-static void ReleaseThreadCtx(void *ptr)
-{
-    ALCcontext *context = ptr;
-    uint ref = DecrementRef(&context->ref);
-    TRACEREF("%p decreasing refcount to %u\n", context, ref);
-    ERR("Context %p current for thread being destroyed, possible leak!\n", context);
-}
-
-/* VerifyContext
- *
- * Checks that the given context is valid, and increments its reference count.
- */
-static ALCboolean VerifyContext(ALCcontext **context)
-{
-    ALCdevice *dev;
-
-    LockLists();
-    dev = ATOMIC_LOAD_SEQ(&DeviceList);
-    while(dev)
-    {
-        ALCcontext *ctx = ATOMIC_LOAD(&dev->ContextList, almemory_order_acquire);
-        while(ctx)
-        {
-            if(ctx == *context)
-            {
-                ALCcontext_IncRef(ctx);
-                UnlockLists();
-                return ALC_TRUE;
-            }
-            ctx = ATOMIC_LOAD(&ctx->next, almemory_order_relaxed);
-        }
-        dev = ATOMIC_LOAD(&dev->next, almemory_order_relaxed);
-    }
-    UnlockLists();
-
-    *context = NULL;
-    return ALC_FALSE;
-}
-
-
-/* GetContextRef
- *
- * Returns the currently active context for this thread, and adds a reference
- * without locking it.
- */
-ALCcontext *GetContextRef(void)
-{
-    ALCcontext *context;
-
-    context = altss_get(LocalContext);
-    if(context)
-        ALCcontext_IncRef(context);
-    else
-    {
-        LockLists();
-        context = ATOMIC_LOAD_SEQ(&GlobalContext);
-        if(context)
-            ALCcontext_IncRef(context);
-        UnlockLists();
-    }
-
-    return context;
-}
-
-
-void AllocateVoices(ALCcontext *context, ALsizei num_voices, ALsizei old_sends)
-{
-    ALCdevice *device = context->Device;
-    ALsizei num_sends = device->NumAuxSends;
-    struct ALvoiceProps *props;
-    size_t sizeof_props;
-    size_t sizeof_voice;
-    ALvoice **voices;
-    ALvoice *voice;
-    ALsizei v = 0;
-    size_t size;
-
-    if(num_voices == context->MaxVoices && num_sends == old_sends)
-        return;
-
-    /* Allocate the voice pointers, voices, and the voices' stored source
-     * property set (including the dynamically-sized Send[] array) in one
-     * chunk.
-     */
-    sizeof_voice = RoundUp(FAM_SIZE(ALvoice, Send, num_sends), 16);
-    sizeof_props = RoundUp(FAM_SIZE(struct ALvoiceProps, Send, num_sends), 16);
-    size = sizeof(ALvoice*) + sizeof_voice + sizeof_props;
-
-    voices = al_calloc(16, RoundUp(size*num_voices, 16));
-    /* The voice and property objects are stored interleaved since they're
-     * paired together.
-     */
-    voice = (ALvoice*)((char*)voices + RoundUp(num_voices*sizeof(ALvoice*), 16));
-    props = (struct ALvoiceProps*)((char*)voice + sizeof_voice);
-
-    if(context->Voices)
-    {
-        const ALsizei v_count = mini(context->VoiceCount, num_voices);
-        const ALsizei s_count = mini(old_sends, num_sends);
-
-        for(;v < v_count;v++)
-        {
-            ALvoice *old_voice = context->Voices[v];
-            ALsizei i;
-
-            /* Copy the old voice data and source property set to the new
-             * storage.
-             */
-            *voice = *old_voice;
-            for(i = 0;i < s_count;i++)
-                voice->Send[i] = old_voice->Send[i];
-            *props = *(old_voice->Props);
-            for(i = 0;i < s_count;i++)
-                props->Send[i] = old_voice->Props->Send[i];
-
-            /* Set this voice's property set pointer and voice reference. */
-            voice->Props = props;
-            voices[v] = voice;
-
-            /* Increment pointers to the next storage space. */
-            voice = (ALvoice*)((char*)props + sizeof_props);
-            props = (struct ALvoiceProps*)((char*)voice + sizeof_voice);
-        }
-        /* Deinit any left over voices that weren't copied over to the new
-         * array. NOTE: If this does anything, v equals num_voices and
-         * num_voices is less than VoiceCount, so the following loop won't do
-         * anything.
-         */
-        for(;v < context->VoiceCount;v++)
-            DeinitVoice(context->Voices[v]);
-    }
-    /* Finish setting the voices' property set pointers and references. */
-    for(;v < num_voices;v++)
-    {
-        ATOMIC_INIT(&voice->Update, NULL);
-
-        voice->Props = props;
-        voices[v] = voice;
-
-        voice = (ALvoice*)((char*)props + sizeof_props);
-        props = (struct ALvoiceProps*)((char*)voice + sizeof_voice);
-    }
-
-    al_free(context->Voices);
-    context->Voices = voices;
-    context->MaxVoices = num_voices;
-    context->VoiceCount = mini(context->VoiceCount, num_voices);
-}
-
-
-/************************************************
- * Standard ALC functions
- ************************************************/
-
-/* alcGetError
- *
- * Return last ALC generated error code for the given device
-*/
-ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)
-{
-    ALCenum errorCode;
-
-    if(VerifyDevice(&device))
-    {
-        errorCode = ATOMIC_EXCHANGE_SEQ(&device->LastError, ALC_NO_ERROR);
-        ALCdevice_DecRef(device);
-    }
-    else
-        errorCode = ATOMIC_EXCHANGE_SEQ(&LastNullDeviceError, ALC_NO_ERROR);
-
-    return errorCode;
-}
-
-
-/* alcSuspendContext
- *
- * Suspends updates for the given context
- */
-ALC_API ALCvoid ALC_APIENTRY alcSuspendContext(ALCcontext *context)
-{
-    if(!SuspendDefers)
-        return;
-
-    if(!VerifyContext(&context))
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-    else
-    {
-        ALCcontext_DeferUpdates(context);
-        ALCcontext_DecRef(context);
-    }
-}
-
-/* alcProcessContext
- *
- * Resumes processing updates for the given context
- */
-ALC_API ALCvoid ALC_APIENTRY alcProcessContext(ALCcontext *context)
-{
-    if(!SuspendDefers)
-        return;
-
-    if(!VerifyContext(&context))
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-    else
-    {
-        ALCcontext_ProcessUpdates(context);
-        ALCcontext_DecRef(context);
-    }
-}
-
-
-/* alcGetString
- *
- * Returns information about the device, and error strings
- */
-ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param)
-{
-    const ALCchar *value = NULL;
-
-    switch(param)
-    {
-    case ALC_NO_ERROR:
-        value = alcNoError;
-        break;
-
-    case ALC_INVALID_ENUM:
-        value = alcErrInvalidEnum;
-        break;
-
-    case ALC_INVALID_VALUE:
-        value = alcErrInvalidValue;
-        break;
-
-    case ALC_INVALID_DEVICE:
-        value = alcErrInvalidDevice;
-        break;
-
-    case ALC_INVALID_CONTEXT:
-        value = alcErrInvalidContext;
-        break;
-
-    case ALC_OUT_OF_MEMORY:
-        value = alcErrOutOfMemory;
-        break;
-
-    case ALC_DEVICE_SPECIFIER:
-        value = alcDefaultName;
-        break;
-
-    case ALC_ALL_DEVICES_SPECIFIER:
-        if(VerifyDevice(&Device))
-        {
-            value = alstr_get_cstr(Device->DeviceName);
-            ALCdevice_DecRef(Device);
-        }
-        else
-        {
-            ProbeAllDevicesList();
-            value = alstr_get_cstr(alcAllDevicesList);
-        }
-        break;
-
-    case ALC_CAPTURE_DEVICE_SPECIFIER:
-        if(VerifyDevice(&Device))
-        {
-            value = alstr_get_cstr(Device->DeviceName);
-            ALCdevice_DecRef(Device);
-        }
-        else
-        {
-            ProbeCaptureDeviceList();
-            value = alstr_get_cstr(alcCaptureDeviceList);
-        }
-        break;
-
-    /* Default devices are always first in the list */
-    case ALC_DEFAULT_DEVICE_SPECIFIER:
-        value = alcDefaultName;
-        break;
-
-    case ALC_DEFAULT_ALL_DEVICES_SPECIFIER:
-        if(alstr_empty(alcAllDevicesList))
-            ProbeAllDevicesList();
-
-        VerifyDevice(&Device);
-
-        free(alcDefaultAllDevicesSpecifier);
-        alcDefaultAllDevicesSpecifier = strdup(alstr_get_cstr(alcAllDevicesList));
-        if(!alcDefaultAllDevicesSpecifier)
-            alcSetError(Device, ALC_OUT_OF_MEMORY);
-
-        value = alcDefaultAllDevicesSpecifier;
-        if(Device) ALCdevice_DecRef(Device);
-        break;
-
-    case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER:
-        if(alstr_empty(alcCaptureDeviceList))
-            ProbeCaptureDeviceList();
-
-        VerifyDevice(&Device);
-
-        free(alcCaptureDefaultDeviceSpecifier);
-        alcCaptureDefaultDeviceSpecifier = strdup(alstr_get_cstr(alcCaptureDeviceList));
-        if(!alcCaptureDefaultDeviceSpecifier)
-            alcSetError(Device, ALC_OUT_OF_MEMORY);
-
-        value = alcCaptureDefaultDeviceSpecifier;
-        if(Device) ALCdevice_DecRef(Device);
-        break;
-
-    case ALC_EXTENSIONS:
-        if(!VerifyDevice(&Device))
-            value = alcNoDeviceExtList;
-        else
-        {
-            value = alcExtensionList;
-            ALCdevice_DecRef(Device);
-        }
-        break;
-
-    case ALC_HRTF_SPECIFIER_SOFT:
-        if(!VerifyDevice(&Device))
-            alcSetError(NULL, ALC_INVALID_DEVICE);
-        else
-        {
-            almtx_lock(&Device->BackendLock);
-            value = (Device->HrtfHandle ? alstr_get_cstr(Device->HrtfName) : "");
-            almtx_unlock(&Device->BackendLock);
-            ALCdevice_DecRef(Device);
-        }
-        break;
-
-    default:
-        VerifyDevice(&Device);
-        alcSetError(Device, ALC_INVALID_ENUM);
-        if(Device) ALCdevice_DecRef(Device);
-        break;
-    }
-
-    return value;
-}
-
-
-static inline ALCsizei NumAttrsForDevice(ALCdevice *device)
-{
-    if(device->Type == Capture) return 9;
-    if(device->Type != Loopback) return 29;
-    if(device->FmtChans == DevFmtAmbi3D)
-        return 35;
-    return 29;
-}
-
-static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values)
-{
-    ALCsizei i;
-
-    if(size <= 0 || values == NULL)
-    {
-        alcSetError(device, ALC_INVALID_VALUE);
-        return 0;
-    }
-
-    if(!device)
-    {
-        switch(param)
-        {
-            case ALC_MAJOR_VERSION:
-                values[0] = alcMajorVersion;
-                return 1;
-            case ALC_MINOR_VERSION:
-                values[0] = alcMinorVersion;
-                return 1;
-
-            case ALC_ATTRIBUTES_SIZE:
-            case ALC_ALL_ATTRIBUTES:
-            case ALC_FREQUENCY:
-            case ALC_REFRESH:
-            case ALC_SYNC:
-            case ALC_MONO_SOURCES:
-            case ALC_STEREO_SOURCES:
-            case ALC_CAPTURE_SAMPLES:
-            case ALC_FORMAT_CHANNELS_SOFT:
-            case ALC_FORMAT_TYPE_SOFT:
-            case ALC_AMBISONIC_LAYOUT_SOFT:
-            case ALC_AMBISONIC_SCALING_SOFT:
-            case ALC_AMBISONIC_ORDER_SOFT:
-            case ALC_MAX_AMBISONIC_ORDER_SOFT:
-                alcSetError(NULL, ALC_INVALID_DEVICE);
-                return 0;
-
-            default:
-                alcSetError(NULL, ALC_INVALID_ENUM);
-                return 0;
-        }
-        return 0;
-    }
-
-    if(device->Type == Capture)
-    {
-        switch(param)
-        {
-            case ALC_ATTRIBUTES_SIZE:
-                values[0] = NumAttrsForDevice(device);
-                return 1;
-
-            case ALC_ALL_ATTRIBUTES:
-                if(size < NumAttrsForDevice(device))
-                {
-                    alcSetError(device, ALC_INVALID_VALUE);
-                    return 0;
-                }
-
-                i = 0;
-                almtx_lock(&device->BackendLock);
-                values[i++] = ALC_MAJOR_VERSION;
-                values[i++] = alcMajorVersion;
-                values[i++] = ALC_MINOR_VERSION;
-                values[i++] = alcMinorVersion;
-                values[i++] = ALC_CAPTURE_SAMPLES;
-                values[i++] = V0(device->Backend,availableSamples)();
-                values[i++] = ALC_CONNECTED;
-                values[i++] = ATOMIC_LOAD(&device->Connected, almemory_order_relaxed);
-                almtx_unlock(&device->BackendLock);
-
-                values[i++] = 0;
-                return i;
-
-            case ALC_MAJOR_VERSION:
-                values[0] = alcMajorVersion;
-                return 1;
-            case ALC_MINOR_VERSION:
-                values[0] = alcMinorVersion;
-                return 1;
-
-            case ALC_CAPTURE_SAMPLES:
-                almtx_lock(&device->BackendLock);
-                values[0] = V0(device->Backend,availableSamples)();
-                almtx_unlock(&device->BackendLock);
-                return 1;
-
-            case ALC_CONNECTED:
-                values[0] = ATOMIC_LOAD(&device->Connected, almemory_order_acquire);
-                return 1;
-
-            default:
-                alcSetError(device, ALC_INVALID_ENUM);
-                return 0;
-        }
-        return 0;
-    }
-
-    /* render device */
-    switch(param)
-    {
-        case ALC_ATTRIBUTES_SIZE:
-            values[0] = NumAttrsForDevice(device);
-            return 1;
-
-        case ALC_ALL_ATTRIBUTES:
-            if(size < NumAttrsForDevice(device))
-            {
-                alcSetError(device, ALC_INVALID_VALUE);
-                return 0;
-            }
-
-            i = 0;
-            almtx_lock(&device->BackendLock);
-            values[i++] = ALC_MAJOR_VERSION;
-            values[i++] = alcMajorVersion;
-            values[i++] = ALC_MINOR_VERSION;
-            values[i++] = alcMinorVersion;
-            values[i++] = ALC_EFX_MAJOR_VERSION;
-            values[i++] = alcEFXMajorVersion;
-            values[i++] = ALC_EFX_MINOR_VERSION;
-            values[i++] = alcEFXMinorVersion;
-
-            values[i++] = ALC_FREQUENCY;
-            values[i++] = device->Frequency;
-            if(device->Type != Loopback)
-            {
-                values[i++] = ALC_REFRESH;
-                values[i++] = device->Frequency / device->UpdateSize;
-
-                values[i++] = ALC_SYNC;
-                values[i++] = ALC_FALSE;
-            }
-            else
-            {
-                if(device->FmtChans == DevFmtAmbi3D)
-                {
-                    values[i++] = ALC_AMBISONIC_LAYOUT_SOFT;
-                    values[i++] = device->AmbiLayout;
-
-                    values[i++] = ALC_AMBISONIC_SCALING_SOFT;
-                    values[i++] = device->AmbiScale;
-
-                    values[i++] = ALC_AMBISONIC_ORDER_SOFT;
-                    values[i++] = device->AmbiOrder;
-                }
-
-                values[i++] = ALC_FORMAT_CHANNELS_SOFT;
-                values[i++] = device->FmtChans;
-
-                values[i++] = ALC_FORMAT_TYPE_SOFT;
-                values[i++] = device->FmtType;
-            }
-
-            values[i++] = ALC_MONO_SOURCES;
-            values[i++] = device->NumMonoSources;
-
-            values[i++] = ALC_STEREO_SOURCES;
-            values[i++] = device->NumStereoSources;
-
-            values[i++] = ALC_MAX_AUXILIARY_SENDS;
-            values[i++] = device->NumAuxSends;
-
-            values[i++] = ALC_HRTF_SOFT;
-            values[i++] = (device->HrtfHandle ? ALC_TRUE : ALC_FALSE);
-
-            values[i++] = ALC_HRTF_STATUS_SOFT;
-            values[i++] = device->HrtfStatus;
-
-            values[i++] = ALC_OUTPUT_LIMITER_SOFT;
-            values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE;
-
-            values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT;
-            values[i++] = MAX_AMBI_ORDER;
-            almtx_unlock(&device->BackendLock);
-
-            values[i++] = 0;
-            return i;
-
-        case ALC_MAJOR_VERSION:
-            values[0] = alcMajorVersion;
-            return 1;
-
-        case ALC_MINOR_VERSION:
-            values[0] = alcMinorVersion;
-            return 1;
-
-        case ALC_EFX_MAJOR_VERSION:
-            values[0] = alcEFXMajorVersion;
-            return 1;
-
-        case ALC_EFX_MINOR_VERSION:
-            values[0] = alcEFXMinorVersion;
-            return 1;
-
-        case ALC_FREQUENCY:
-            values[0] = device->Frequency;
-            return 1;
-
-        case ALC_REFRESH:
-            if(device->Type == Loopback)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            almtx_lock(&device->BackendLock);
-            values[0] = device->Frequency / device->UpdateSize;
-            almtx_unlock(&device->BackendLock);
-            return 1;
-
-        case ALC_SYNC:
-            if(device->Type == Loopback)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = ALC_FALSE;
-            return 1;
-
-        case ALC_FORMAT_CHANNELS_SOFT:
-            if(device->Type != Loopback)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = device->FmtChans;
-            return 1;
-
-        case ALC_FORMAT_TYPE_SOFT:
-            if(device->Type != Loopback)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = device->FmtType;
-            return 1;
-
-        case ALC_AMBISONIC_LAYOUT_SOFT:
-            if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = device->AmbiLayout;
-            return 1;
-
-        case ALC_AMBISONIC_SCALING_SOFT:
-            if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = device->AmbiScale;
-            return 1;
-
-        case ALC_AMBISONIC_ORDER_SOFT:
-            if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D)
-            {
-                alcSetError(device, ALC_INVALID_DEVICE);
-                return 0;
-            }
-            values[0] = device->AmbiOrder;
-            return 1;
-
-        case ALC_MONO_SOURCES:
-            values[0] = device->NumMonoSources;
-            return 1;
-
-        case ALC_STEREO_SOURCES:
-            values[0] = device->NumStereoSources;
-            return 1;
-
-        case ALC_MAX_AUXILIARY_SENDS:
-            values[0] = device->NumAuxSends;
-            return 1;
-
-        case ALC_CONNECTED:
-            values[0] = ATOMIC_LOAD(&device->Connected, almemory_order_acquire);
-            return 1;
-
-        case ALC_HRTF_SOFT:
-            values[0] = (device->HrtfHandle ? ALC_TRUE : ALC_FALSE);
-            return 1;
-
-        case ALC_HRTF_STATUS_SOFT:
-            values[0] = device->HrtfStatus;
-            return 1;
-
-        case ALC_NUM_HRTF_SPECIFIERS_SOFT:
-            almtx_lock(&device->BackendLock);
-            FreeHrtfList(&device->HrtfList);
-            device->HrtfList = EnumerateHrtf(device->DeviceName);
-            values[0] = (ALCint)VECTOR_SIZE(device->HrtfList);
-            almtx_unlock(&device->BackendLock);
-            return 1;
-
-        case ALC_OUTPUT_LIMITER_SOFT:
-            values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE;
-            return 1;
-
-        case ALC_MAX_AMBISONIC_ORDER_SOFT:
-            values[0] = MAX_AMBI_ORDER;
-            return 1;
-
-        default:
-            alcSetError(device, ALC_INVALID_ENUM);
-            return 0;
-    }
-    return 0;
-}
-
-/* alcGetIntegerv
- *
- * Returns information about the device and the version of OpenAL
- */
-ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values)
-{
-    VerifyDevice(&device);
-    if(size <= 0 || values == NULL)
-        alcSetError(device, ALC_INVALID_VALUE);
-    else
-        GetIntegerv(device, param, size, values);
-    if(device) ALCdevice_DecRef(device);
-}
-
-ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values)
-{
-    ALCint *ivals;
-    ALsizei i;
-
-    VerifyDevice(&device);
-    if(size <= 0 || values == NULL)
-        alcSetError(device, ALC_INVALID_VALUE);
-    else if(!device || device->Type == Capture)
-    {
-        ivals = malloc(size * sizeof(ALCint));
-        size = GetIntegerv(device, pname, size, ivals);
-        for(i = 0;i < size;i++)
-            values[i] = ivals[i];
-        free(ivals);
-    }
-    else /* render device */
-    {
-        ClockLatency clock;
-        ALuint64 basecount;
-        ALuint samplecount;
-        ALuint refcount;
-
-        switch(pname)
-        {
-            case ALC_ATTRIBUTES_SIZE:
-                *values = NumAttrsForDevice(device)+4;
-                break;
-
-            case ALC_ALL_ATTRIBUTES:
-                if(size < NumAttrsForDevice(device)+4)
-                    alcSetError(device, ALC_INVALID_VALUE);
-                else
-                {
-                    i = 0;
-                    almtx_lock(&device->BackendLock);
-                    values[i++] = ALC_FREQUENCY;
-                    values[i++] = device->Frequency;
-
-                    if(device->Type != Loopback)
-                    {
-                        values[i++] = ALC_REFRESH;
-                        values[i++] = device->Frequency / device->UpdateSize;
-
-                        values[i++] = ALC_SYNC;
-                        values[i++] = ALC_FALSE;
-                    }
-                    else
-                    {
-                        if(device->FmtChans == DevFmtAmbi3D)
-                        {
-                            values[i++] = ALC_AMBISONIC_LAYOUT_SOFT;
-                            values[i++] = device->AmbiLayout;
-
-                            values[i++] = ALC_AMBISONIC_SCALING_SOFT;
-                            values[i++] = device->AmbiScale;
-
-                            values[i++] = ALC_AMBISONIC_ORDER_SOFT;
-                            values[i++] = device->AmbiOrder;
-                        }
-
-                        values[i++] = ALC_FORMAT_CHANNELS_SOFT;
-                        values[i++] = device->FmtChans;
-
-                        values[i++] = ALC_FORMAT_TYPE_SOFT;
-                        values[i++] = device->FmtType;
-                    }
-
-                    values[i++] = ALC_MONO_SOURCES;
-                    values[i++] = device->NumMonoSources;
-
-                    values[i++] = ALC_STEREO_SOURCES;
-                    values[i++] = device->NumStereoSources;
-
-                    values[i++] = ALC_MAX_AUXILIARY_SENDS;
-                    values[i++] = device->NumAuxSends;
-
-                    values[i++] = ALC_HRTF_SOFT;
-                    values[i++] = (device->HrtfHandle ? ALC_TRUE : ALC_FALSE);
-
-                    values[i++] = ALC_HRTF_STATUS_SOFT;
-                    values[i++] = device->HrtfStatus;
-
-                    values[i++] = ALC_OUTPUT_LIMITER_SOFT;
-                    values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE;
-
-                    clock = V0(device->Backend,getClockLatency)();
-                    values[i++] = ALC_DEVICE_CLOCK_SOFT;
-                    values[i++] = clock.ClockTime;
-
-                    values[i++] = ALC_DEVICE_LATENCY_SOFT;
-                    values[i++] = clock.Latency;
-                    almtx_unlock(&device->BackendLock);
-
-                    values[i++] = 0;
-                }
-                break;
-
-            case ALC_DEVICE_CLOCK_SOFT:
-                almtx_lock(&device->BackendLock);
-                do {
-                    while(((refcount=ReadRef(&device->MixCount))&1) != 0)
-                        althrd_yield();
-                    basecount = device->ClockBase;
-                    samplecount = device->SamplesDone;
-                } while(refcount != ReadRef(&device->MixCount));
-                *values = basecount + (samplecount*DEVICE_CLOCK_RES/device->Frequency);
-                almtx_unlock(&device->BackendLock);
-                break;
-
-            case ALC_DEVICE_LATENCY_SOFT:
-                almtx_lock(&device->BackendLock);
-                clock = V0(device->Backend,getClockLatency)();
-                almtx_unlock(&device->BackendLock);
-                *values = clock.Latency;
-                break;
-
-            case ALC_DEVICE_CLOCK_LATENCY_SOFT:
-                if(size < 2)
-                    alcSetError(device, ALC_INVALID_VALUE);
-                else
-                {
-                    almtx_lock(&device->BackendLock);
-                    clock = V0(device->Backend,getClockLatency)();
-                    almtx_unlock(&device->BackendLock);
-                    values[0] = clock.ClockTime;
-                    values[1] = clock.Latency;
-                }
-                break;
-
-            default:
-                ivals = malloc(size * sizeof(ALCint));
-                size = GetIntegerv(device, pname, size, ivals);
-                for(i = 0;i < size;i++)
-                    values[i] = ivals[i];
-                free(ivals);
-                break;
-        }
-    }
-    if(device)
-        ALCdevice_DecRef(device);
-}
-
-
-/* alcIsExtensionPresent
- *
- * Determines if there is support for a particular extension
- */
-ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName)
-{
-    ALCboolean bResult = ALC_FALSE;
-
-    VerifyDevice(&device);
-
-    if(!extName)
-        alcSetError(device, ALC_INVALID_VALUE);
-    else
-    {
-        size_t len = strlen(extName);
-        const char *ptr = (device ? alcExtensionList : alcNoDeviceExtList);
-        while(ptr && *ptr)
-        {
-            if(strncasecmp(ptr, extName, len) == 0 &&
-               (ptr[len] == '\0' || isspace(ptr[len])))
-            {
-                bResult = ALC_TRUE;
-                break;
-            }
-            if((ptr=strchr(ptr, ' ')) != NULL)
-            {
-                do {
-                    ++ptr;
-                } while(isspace(*ptr));
-            }
-        }
-    }
-    if(device)
-        ALCdevice_DecRef(device);
-    return bResult;
-}
-
-
-/* alcGetProcAddress
- *
- * Retrieves the function address for a particular extension function
- */
-ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName)
-{
-    ALCvoid *ptr = NULL;
-
-    if(!funcName)
-    {
-        VerifyDevice(&device);
-        alcSetError(device, ALC_INVALID_VALUE);
-        if(device) ALCdevice_DecRef(device);
-    }
-    else
-    {
-        size_t i = 0;
-        for(i = 0;i < COUNTOF(alcFunctions);i++)
-        {
-            if(strcmp(alcFunctions[i].funcName, funcName) == 0)
-            {
-                ptr = alcFunctions[i].address;
-                break;
-            }
-        }
-    }
-
-    return ptr;
-}
-
-
-/* alcGetEnumValue
- *
- * Get the value for a particular ALC enumeration name
- */
-ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName)
-{
-    ALCenum val = 0;
-
-    if(!enumName)
-    {
-        VerifyDevice(&device);
-        alcSetError(device, ALC_INVALID_VALUE);
-        if(device) ALCdevice_DecRef(device);
-    }
-    else
-    {
-        size_t i = 0;
-        for(i = 0;i < COUNTOF(alcEnumerations);i++)
-        {
-            if(strcmp(alcEnumerations[i].enumName, enumName) == 0)
-            {
-                val = alcEnumerations[i].value;
-                break;
-            }
-        }
-    }
-
-    return val;
-}
-
-
-/* alcCreateContext
- *
- * Create and attach a context to the given device.
- */
-ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList)
-{
-    ALCcontext *ALContext;
-    ALfloat valf;
-    ALCenum err;
-
-    /* Explicitly hold the list lock while taking the BackendLock in case the
-     * device is asynchronously destropyed, to ensure this new context is
-     * properly cleaned up after being made.
-     */
-    LockLists();
-    if(!VerifyDevice(&device) || device->Type == Capture ||
-       !ATOMIC_LOAD(&device->Connected, almemory_order_relaxed))
-    {
-        UnlockLists();
-        alcSetError(device, ALC_INVALID_DEVICE);
-        if(device) ALCdevice_DecRef(device);
-        return NULL;
-    }
-    almtx_lock(&device->BackendLock);
-    UnlockLists();
-
-    ATOMIC_STORE_SEQ(&device->LastError, ALC_NO_ERROR);
-
-    if(device->Type == Playback && DefaultEffect.type != AL_EFFECT_NULL)
-        ALContext = al_calloc(16, sizeof(ALCcontext)+sizeof(ALlistener)+sizeof(ALeffectslot));
-    else
-        ALContext = al_calloc(16, sizeof(ALCcontext)+sizeof(ALlistener));
-    if(!ALContext)
-    {
-        almtx_unlock(&device->BackendLock);
-
-        alcSetError(device, ALC_OUT_OF_MEMORY);
-        ALCdevice_DecRef(device);
-        return NULL;
-    }
-
-    InitRef(&ALContext->ref, 1);
-    ALContext->Listener = (ALlistener*)ALContext->_listener_mem;
-    ALContext->DefaultSlot = NULL;
-
-    ALContext->Voices = NULL;
-    ALContext->VoiceCount = 0;
-    ALContext->MaxVoices = 0;
-    ATOMIC_INIT(&ALContext->ActiveAuxSlots, NULL);
-    ALContext->Device = device;
-    ATOMIC_INIT(&ALContext->next, NULL);
-
-    if((err=UpdateDeviceParams(device, attrList)) != ALC_NO_ERROR)
-    {
-        almtx_unlock(&device->BackendLock);
-
-        al_free(ALContext);
-        ALContext = NULL;
-
-        alcSetError(device, err);
-        if(err == ALC_INVALID_DEVICE)
-        {
-            V0(device->Backend,lock)();
-            aluHandleDisconnect(device, "Device update failure");
-            V0(device->Backend,unlock)();
-        }
-        ALCdevice_DecRef(device);
-        return NULL;
-    }
-    AllocateVoices(ALContext, 256, device->NumAuxSends);
-
-    if(DefaultEffect.type != AL_EFFECT_NULL && device->Type == Playback)
-    {
-        ALContext->DefaultSlot = (ALeffectslot*)(ALContext->_listener_mem + sizeof(ALlistener));
-        if(InitEffectSlot(ALContext->DefaultSlot) == AL_NO_ERROR)
-            aluInitEffectPanning(ALContext->DefaultSlot);
-        else
-        {
-            ALContext->DefaultSlot = NULL;
-            ERR("Failed to initialize the default effect slot\n");
-        }
-    }
-
-    ALCdevice_IncRef(ALContext->Device);
-    InitContext(ALContext);
-
-    if(ConfigValueFloat(alstr_get_cstr(device->DeviceName), NULL, "volume-adjust", &valf))
-    {
-        if(!isfinite(valf))
-            ERR("volume-adjust must be finite: %f\n", valf);
-        else
-        {
-            ALfloat db = clampf(valf, -24.0f, 24.0f);
-            if(db != valf)
-                WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f);
-            ALContext->GainBoost = powf(10.0f, db/20.0f);
-            TRACE("volume-adjust gain: %f\n", ALContext->GainBoost);
-        }
-    }
-    UpdateListenerProps(ALContext);
-
-    {
-        ALCcontext *head = ATOMIC_LOAD_SEQ(&device->ContextList);
-        do {
-            ATOMIC_STORE(&ALContext->next, head, almemory_order_relaxed);
-        } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK_SEQ(&device->ContextList, &head,
-                                                     ALContext) == 0);
-    }
-    almtx_unlock(&device->BackendLock);
-
-    if(ALContext->DefaultSlot)
-    {
-        if(InitializeEffect(ALContext, ALContext->DefaultSlot, &DefaultEffect) == AL_NO_ERROR)
-            UpdateEffectSlotProps(ALContext->DefaultSlot, ALContext);
-        else
-            ERR("Failed to initialize the default effect\n");
-    }
-
-    ALCdevice_DecRef(device);
-
-    TRACE("Created context %p\n", ALContext);
-    return ALContext;
-}
-
-/* alcDestroyContext
- *
- * Remove a context from its device
- */
-ALC_API ALCvoid ALC_APIENTRY alcDestroyContext(ALCcontext *context)
-{
-    ALCdevice *Device;
-
-    LockLists();
-    if(!VerifyContext(&context))
-    {
-        UnlockLists();
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-        return;
-    }
-
-    Device = context->Device;
-    if(Device)
-    {
-        almtx_lock(&Device->BackendLock);
-        if(!ReleaseContext(context, Device))
-        {
-            V0(Device->Backend,stop)();
-            Device->Flags &= ~DEVICE_RUNNING;
-        }
-        almtx_unlock(&Device->BackendLock);
-    }
-    UnlockLists();
-
-    ALCcontext_DecRef(context);
-}
-
-
-/* alcGetCurrentContext
- *
- * Returns the currently active context on the calling thread
- */
-ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void)
-{
-    ALCcontext *Context = altss_get(LocalContext);
-    if(!Context) Context = ATOMIC_LOAD_SEQ(&GlobalContext);
-    return Context;
-}
-
-/* alcGetThreadContext
- *
- * Returns the currently active thread-local context
- */
-ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void)
-{
-    return altss_get(LocalContext);
-}
-
-
-/* alcMakeContextCurrent
- *
- * Makes the given context the active process-wide context, and removes the
- * thread-local context for the calling thread.
- */
-ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context)
-{
-    /* context must be valid or NULL */
-    if(context && !VerifyContext(&context))
-    {
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-        return ALC_FALSE;
-    }
-    /* context's reference count is already incremented */
-    context = ATOMIC_EXCHANGE_PTR_SEQ(&GlobalContext, context);
-    if(context) ALCcontext_DecRef(context);
-
-    if((context=altss_get(LocalContext)) != NULL)
-    {
-        altss_set(LocalContext, NULL);
-        ALCcontext_DecRef(context);
-    }
-
-    return ALC_TRUE;
-}
-
-/* alcSetThreadContext
- *
- * Makes the given context the active context for the current thread
- */
-ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context)
-{
-    ALCcontext *old;
-
-    /* context must be valid or NULL */
-    if(context && !VerifyContext(&context))
-    {
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-        return ALC_FALSE;
-    }
-    /* context's reference count is already incremented */
-    old = altss_get(LocalContext);
-    altss_set(LocalContext, context);
-    if(old) ALCcontext_DecRef(old);
-
-    return ALC_TRUE;
-}
-
-
-/* alcGetContextsDevice
- *
- * Returns the device that a particular context is attached to
- */
-ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context)
-{
-    ALCdevice *Device;
-
-    if(!VerifyContext(&Context))
-    {
-        alcSetError(NULL, ALC_INVALID_CONTEXT);
-        return NULL;
-    }
-    Device = Context->Device;
-    ALCcontext_DecRef(Context);
-
-    return Device;
-}
-
-
-/* alcOpenDevice
- *
- * Opens the named device.
- */
-ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName)
-{
-    ALCbackendFactory *factory;
-    const ALCchar *fmt;
-    ALCdevice *device;
-    ALCenum err;
-
-    DO_INITCONFIG();
-
-    if(!PlaybackBackend.name)
-    {
-        alcSetError(NULL, ALC_INVALID_VALUE);
-        return NULL;
-    }
-
-    if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0
-#ifdef _WIN32
-        /* Some old Windows apps hardcode these expecting OpenAL to use a
-         * specific audio API, even when they're not enumerated. Creative's
-         * router effectively ignores them too.
-         */
-        || strcasecmp(deviceName, "DirectSound3D") == 0 || strcasecmp(deviceName, "DirectSound") == 0
-        || strcasecmp(deviceName, "MMSYSTEM") == 0
-#endif
-    ))
-        deviceName = NULL;
-
-    device = al_calloc(16, sizeof(ALCdevice));
-    if(!device)
-    {
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    //Validate device
-    InitDevice(device, Playback);
-
-    //Set output format
-    device->FmtChans = DevFmtChannelsDefault;
-    device->FmtType = DevFmtTypeDefault;
-    device->Frequency = DEFAULT_OUTPUT_RATE;
-    device->IsHeadphones = AL_FALSE;
-    device->AmbiLayout = AmbiLayout_Default;
-    device->AmbiScale = AmbiNorm_Default;
-    device->NumUpdates = 3;
-    device->UpdateSize = 1024;
-
-    device->SourcesMax = 256;
-    device->AuxiliaryEffectSlotMax = 64;
-    device->NumAuxSends = DEFAULT_SENDS;
-
-    if(ConfigValueStr(deviceName, NULL, "channels", &fmt))
-    {
-        static const struct {
-            const char name[16];
-            enum DevFmtChannels chans;
-            ALsizei order;
-        } chanlist[] = {
-            { "mono",       DevFmtMono,   0 },
-            { "stereo",     DevFmtStereo, 0 },
-            { "quad",       DevFmtQuad,   0 },
-            { "surround51", DevFmtX51,    0 },
-            { "surround61", DevFmtX61,    0 },
-            { "surround71", DevFmtX71,    0 },
-            { "surround51rear", DevFmtX51Rear, 0 },
-            { "ambi1", DevFmtAmbi3D, 1 },
-            { "ambi2", DevFmtAmbi3D, 2 },
-            { "ambi3", DevFmtAmbi3D, 3 },
-        };
-        size_t i;
-
-        for(i = 0;i < COUNTOF(chanlist);i++)
-        {
-            if(strcasecmp(chanlist[i].name, fmt) == 0)
-            {
-                device->FmtChans = chanlist[i].chans;
-                device->AmbiOrder = chanlist[i].order;
-                device->Flags |= DEVICE_CHANNELS_REQUEST;
-                break;
-            }
-        }
-        if(i == COUNTOF(chanlist))
-            ERR("Unsupported channels: %s\n", fmt);
-    }
-    if(ConfigValueStr(deviceName, NULL, "sample-type", &fmt))
-    {
-        static const struct {
-            const char name[16];
-            enum DevFmtType type;
-        } typelist[] = {
-            { "int8",    DevFmtByte   },
-            { "uint8",   DevFmtUByte  },
-            { "int16",   DevFmtShort  },
-            { "uint16",  DevFmtUShort },
-            { "int32",   DevFmtInt    },
-            { "uint32",  DevFmtUInt   },
-            { "float32", DevFmtFloat  },
-        };
-        size_t i;
-
-        for(i = 0;i < COUNTOF(typelist);i++)
-        {
-            if(strcasecmp(typelist[i].name, fmt) == 0)
-            {
-                device->FmtType = typelist[i].type;
-                device->Flags |= DEVICE_SAMPLE_TYPE_REQUEST;
-                break;
-            }
-        }
-        if(i == COUNTOF(typelist))
-            ERR("Unsupported sample-type: %s\n", fmt);
-    }
-
-    if(ConfigValueUInt(deviceName, NULL, "frequency", &device->Frequency))
-    {
-        device->Flags |= DEVICE_FREQUENCY_REQUEST;
-        if(device->Frequency < MIN_OUTPUT_RATE)
-            ERR("%uhz request clamped to %uhz minimum\n", device->Frequency, MIN_OUTPUT_RATE);
-        device->Frequency = maxu(device->Frequency, MIN_OUTPUT_RATE);
-    }
-
-    ConfigValueUInt(deviceName, NULL, "periods", &device->NumUpdates);
-    device->NumUpdates = clampu(device->NumUpdates, 2, 16);
-
-    ConfigValueUInt(deviceName, NULL, "period_size", &device->UpdateSize);
-    device->UpdateSize = clampu(device->UpdateSize, 64, 8192);
-    if((CPUCapFlags&(CPU_CAP_SSE|CPU_CAP_NEON)) != 0)
-        device->UpdateSize = (device->UpdateSize+3)&~3;
-
-    ConfigValueUInt(deviceName, NULL, "sources", &device->SourcesMax);
-    if(device->SourcesMax == 0) device->SourcesMax = 256;
-
-    ConfigValueUInt(deviceName, NULL, "slots", &device->AuxiliaryEffectSlotMax);
-    if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 64;
-    else device->AuxiliaryEffectSlotMax = minu(device->AuxiliaryEffectSlotMax, INT_MAX);
-
-    if(ConfigValueInt(deviceName, NULL, "sends", &device->NumAuxSends))
-        device->NumAuxSends = clampi(
-            DEFAULT_SENDS, 0, clampi(device->NumAuxSends, 0, MAX_SENDS)
-        );
-
-    device->NumStereoSources = 1;
-    device->NumMonoSources = device->SourcesMax - device->NumStereoSources;
-
-    factory = PlaybackBackend.getFactory();
-    device->Backend = V(factory,createBackend)(device, ALCbackend_Playback);
-    if(!device->Backend)
-    {
-        FreeDevice(device);
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    // Find a playback device to open
-    if((err=V(device->Backend,open)(deviceName)) != ALC_NO_ERROR)
-    {
-        FreeDevice(device);
-        alcSetError(NULL, err);
-        return NULL;
-    }
-
-    if(ConfigValueStr(alstr_get_cstr(device->DeviceName), NULL, "ambi-format", &fmt))
-    {
-        if(strcasecmp(fmt, "fuma") == 0)
-        {
-            device->AmbiLayout = AmbiLayout_FuMa;
-            device->AmbiScale = AmbiNorm_FuMa;
-        }
-        else if(strcasecmp(fmt, "acn+sn3d") == 0)
-        {
-            device->AmbiLayout = AmbiLayout_ACN;
-            device->AmbiScale = AmbiNorm_SN3D;
-        }
-        else if(strcasecmp(fmt, "acn+n3d") == 0)
-        {
-            device->AmbiLayout = AmbiLayout_ACN;
-            device->AmbiScale = AmbiNorm_N3D;
-        }
-        else
-            ERR("Unsupported ambi-format: %s\n", fmt);
-    }
-
-    device->Limiter = CreateDeviceLimiter(device);
-
-    {
-        ALCdevice *head = ATOMIC_LOAD_SEQ(&DeviceList);
-        do {
-            ATOMIC_STORE(&device->next, head, almemory_order_relaxed);
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_WEAK_SEQ(&DeviceList, &head, device));
-    }
-
-    TRACE("Created device %p, \"%s\"\n", device, alstr_get_cstr(device->DeviceName));
-    return device;
-}
-
-/* alcCloseDevice
- *
- * Closes the given device.
- */
-ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device)
-{
-    ALCdevice *iter, *origdev, *nextdev;
-    ALCcontext *ctx;
-
-    LockLists();
-    iter = ATOMIC_LOAD_SEQ(&DeviceList);
-    do {
-        if(iter == device)
-            break;
-        iter = ATOMIC_LOAD(&iter->next, almemory_order_relaxed);
-    } while(iter != NULL);
-    if(!iter || iter->Type == Capture)
-    {
-        alcSetError(iter, ALC_INVALID_DEVICE);
-        UnlockLists();
-        return ALC_FALSE;
-    }
-    almtx_lock(&device->BackendLock);
-
-    origdev = device;
-    nextdev = ATOMIC_LOAD(&device->next, almemory_order_relaxed);
-    if(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&DeviceList, &origdev, nextdev))
-    {
-        ALCdevice *list;
-        do {
-            list = origdev;
-            origdev = device;
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&list->next, &origdev, nextdev));
-    }
-    UnlockLists();
-
-    ctx = ATOMIC_LOAD_SEQ(&device->ContextList);
-    while(ctx != NULL)
-    {
-        ALCcontext *next = ATOMIC_LOAD(&ctx->next, almemory_order_relaxed);
-        WARN("Releasing context %p\n", ctx);
-        ReleaseContext(ctx, device);
-        ctx = next;
-    }
-    if((device->Flags&DEVICE_RUNNING))
-        V0(device->Backend,stop)();
-    device->Flags &= ~DEVICE_RUNNING;
-    almtx_unlock(&device->BackendLock);
-
-    ALCdevice_DecRef(device);
-
-    return ALC_TRUE;
-}
-
-
-/************************************************
- * ALC capture functions
- ************************************************/
-ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples)
-{
-    ALCbackendFactory *factory;
-    ALCdevice *device = NULL;
-    ALCenum err;
-
-    DO_INITCONFIG();
-
-    if(!CaptureBackend.name)
-    {
-        alcSetError(NULL, ALC_INVALID_VALUE);
-        return NULL;
-    }
-
-    if(samples <= 0)
-    {
-        alcSetError(NULL, ALC_INVALID_VALUE);
-        return NULL;
-    }
-
-    if(deviceName && (!deviceName[0] || strcasecmp(deviceName, alcDefaultName) == 0 || strcasecmp(deviceName, "openal-soft") == 0))
-        deviceName = NULL;
-
-    device = al_calloc(16, sizeof(ALCdevice));
-    if(!device)
-    {
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    //Validate device
-    InitDevice(device, Capture);
-
-    device->Frequency = frequency;
-    device->Flags |= DEVICE_FREQUENCY_REQUEST;
-
-    if(DecomposeDevFormat(format, &device->FmtChans, &device->FmtType) == AL_FALSE)
-    {
-        FreeDevice(device);
-        alcSetError(NULL, ALC_INVALID_ENUM);
-        return NULL;
-    }
-    device->Flags |= DEVICE_CHANNELS_REQUEST | DEVICE_SAMPLE_TYPE_REQUEST;
-    device->IsHeadphones = AL_FALSE;
-    device->AmbiOrder = 0;
-    device->AmbiLayout = AmbiLayout_Default;
-    device->AmbiScale = AmbiNorm_Default;
-
-    device->UpdateSize = samples;
-    device->NumUpdates = 1;
-
-    factory = CaptureBackend.getFactory();
-    device->Backend = V(factory,createBackend)(device, ALCbackend_Capture);
-    if(!device->Backend)
-    {
-        FreeDevice(device);
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    TRACE("Capture format: %s, %s, %uhz, %u update size x%d\n",
-        DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
-        device->Frequency, device->UpdateSize, device->NumUpdates
-    );
-    if((err=V(device->Backend,open)(deviceName)) != ALC_NO_ERROR)
-    {
-        FreeDevice(device);
-        alcSetError(NULL, err);
-        return NULL;
-    }
-
-    {
-        ALCdevice *head = ATOMIC_LOAD_SEQ(&DeviceList);
-        do {
-            ATOMIC_STORE(&device->next, head, almemory_order_relaxed);
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_WEAK_SEQ(&DeviceList, &head, device));
-    }
-
-    TRACE("Created device %p, \"%s\"\n", device, alstr_get_cstr(device->DeviceName));
-    return device;
-}
-
-ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device)
-{
-    ALCdevice *iter, *origdev, *nextdev;
-
-    LockLists();
-    iter = ATOMIC_LOAD_SEQ(&DeviceList);
-    do {
-        if(iter == device)
-            break;
-        iter = ATOMIC_LOAD(&iter->next, almemory_order_relaxed);
-    } while(iter != NULL);
-    if(!iter || iter->Type != Capture)
-    {
-        alcSetError(iter, ALC_INVALID_DEVICE);
-        UnlockLists();
-        return ALC_FALSE;
-    }
-
-    origdev = device;
-    nextdev = ATOMIC_LOAD(&device->next, almemory_order_relaxed);
-    if(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&DeviceList, &origdev, nextdev))
-    {
-        ALCdevice *list;
-        do {
-            list = origdev;
-            origdev = device;
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(&list->next, &origdev, nextdev));
-    }
-    UnlockLists();
-
-    ALCdevice_DecRef(device);
-
-    return ALC_TRUE;
-}
-
-ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device)
-{
-    if(!VerifyDevice(&device) || device->Type != Capture)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else
-    {
-        almtx_lock(&device->BackendLock);
-        if(!ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-            alcSetError(device, ALC_INVALID_DEVICE);
-        else if(!(device->Flags&DEVICE_RUNNING))
-        {
-            if(V0(device->Backend,start)())
-                device->Flags |= DEVICE_RUNNING;
-            else
-            {
-                aluHandleDisconnect(device, "Device start failure");
-                alcSetError(device, ALC_INVALID_DEVICE);
-            }
-        }
-        almtx_unlock(&device->BackendLock);
-    }
-
-    if(device) ALCdevice_DecRef(device);
-}
-
-ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device)
-{
-    if(!VerifyDevice(&device) || device->Type != Capture)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else
-    {
-        almtx_lock(&device->BackendLock);
-        if((device->Flags&DEVICE_RUNNING))
-            V0(device->Backend,stop)();
-        device->Flags &= ~DEVICE_RUNNING;
-        almtx_unlock(&device->BackendLock);
-    }
-
-    if(device) ALCdevice_DecRef(device);
-}
-
-ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
-{
-    if(!VerifyDevice(&device) || device->Type != Capture)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else
-    {
-        ALCenum err = ALC_INVALID_VALUE;
-
-        almtx_lock(&device->BackendLock);
-        if(samples >= 0 && V0(device->Backend,availableSamples)() >= (ALCuint)samples)
-            err = V(device->Backend,captureSamples)(buffer, samples);
-        almtx_unlock(&device->BackendLock);
-
-        if(err != ALC_NO_ERROR)
-            alcSetError(device, err);
-    }
-    if(device) ALCdevice_DecRef(device);
-}
-
-
-/************************************************
- * ALC loopback functions
- ************************************************/
-
-/* alcLoopbackOpenDeviceSOFT
- *
- * Open a loopback device, for manual rendering.
- */
-ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName)
-{
-    ALCbackendFactory *factory;
-    ALCdevice *device;
-
-    DO_INITCONFIG();
-
-    /* Make sure the device name, if specified, is us. */
-    if(deviceName && strcmp(deviceName, alcDefaultName) != 0)
-    {
-        alcSetError(NULL, ALC_INVALID_VALUE);
-        return NULL;
-    }
-
-    device = al_calloc(16, sizeof(ALCdevice));
-    if(!device)
-    {
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    //Validate device
-    InitDevice(device, Loopback);
-
-    device->SourcesMax = 256;
-    device->AuxiliaryEffectSlotMax = 64;
-    device->NumAuxSends = DEFAULT_SENDS;
-
-    //Set output format
-    device->NumUpdates = 0;
-    device->UpdateSize = 0;
-
-    device->Frequency = DEFAULT_OUTPUT_RATE;
-    device->FmtChans = DevFmtChannelsDefault;
-    device->FmtType = DevFmtTypeDefault;
-    device->IsHeadphones = AL_FALSE;
-    device->AmbiLayout = AmbiLayout_Default;
-    device->AmbiScale = AmbiNorm_Default;
-
-    ConfigValueUInt(NULL, NULL, "sources", &device->SourcesMax);
-    if(device->SourcesMax == 0) device->SourcesMax = 256;
-
-    ConfigValueUInt(NULL, NULL, "slots", &device->AuxiliaryEffectSlotMax);
-    if(device->AuxiliaryEffectSlotMax == 0) device->AuxiliaryEffectSlotMax = 64;
-    else device->AuxiliaryEffectSlotMax = minu(device->AuxiliaryEffectSlotMax, INT_MAX);
-
-    if(ConfigValueInt(NULL, NULL, "sends", &device->NumAuxSends))
-        device->NumAuxSends = clampi(
-            DEFAULT_SENDS, 0, clampi(device->NumAuxSends, 0, MAX_SENDS)
-        );
-
-    device->NumStereoSources = 1;
-    device->NumMonoSources = device->SourcesMax - device->NumStereoSources;
-
-    factory = ALCloopbackFactory_getFactory();
-    device->Backend = V(factory,createBackend)(device, ALCbackend_Loopback);
-    if(!device->Backend)
-    {
-        al_free(device);
-        alcSetError(NULL, ALC_OUT_OF_MEMORY);
-        return NULL;
-    }
-
-    // Open the "backend"
-    V(device->Backend,open)("Loopback");
-
-    device->Limiter = CreateDeviceLimiter(device);
-
-    {
-        ALCdevice *head = ATOMIC_LOAD_SEQ(&DeviceList);
-        do {
-            ATOMIC_STORE(&device->next, head, almemory_order_relaxed);
-        } while(!ATOMIC_COMPARE_EXCHANGE_PTR_WEAK_SEQ(&DeviceList, &head, device));
-    }
-
-    TRACE("Created device %p\n", device);
-    return device;
-}
-
-/* alcIsRenderFormatSupportedSOFT
- *
- * Determines if the loopback device supports the given format for rendering.
- */
-ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type)
-{
-    ALCboolean ret = ALC_FALSE;
-
-    if(!VerifyDevice(&device) || device->Type != Loopback)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else if(freq <= 0)
-        alcSetError(device, ALC_INVALID_VALUE);
-    else
-    {
-        if(IsValidALCType(type) && IsValidALCChannels(channels) && freq >= MIN_OUTPUT_RATE)
-            ret = ALC_TRUE;
-    }
-    if(device) ALCdevice_DecRef(device);
-
-    return ret;
-}
-
-/* alcRenderSamplesSOFT
- *
- * Renders some samples into a buffer, using the format last set by the
- * attributes given to alcCreateContext.
- */
-FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
-{
-    if(!VerifyDevice(&device) || device->Type != Loopback)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else if(samples < 0 || (samples > 0 && buffer == NULL))
-        alcSetError(device, ALC_INVALID_VALUE);
-    else
-    {
-        V0(device->Backend,lock)();
-        aluMixData(device, buffer, samples);
-        V0(device->Backend,unlock)();
-    }
-    if(device) ALCdevice_DecRef(device);
-}
-
-
-/************************************************
- * ALC DSP pause/resume functions
- ************************************************/
-
-/* alcDevicePauseSOFT
- *
- * Pause the DSP to stop audio processing.
- */
-ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device)
-{
-    if(!VerifyDevice(&device) || device->Type != Playback)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else
-    {
-        almtx_lock(&device->BackendLock);
-        if((device->Flags&DEVICE_RUNNING))
-            V0(device->Backend,stop)();
-        device->Flags &= ~DEVICE_RUNNING;
-        device->Flags |= DEVICE_PAUSED;
-        almtx_unlock(&device->BackendLock);
-    }
-    if(device) ALCdevice_DecRef(device);
-}
-
-/* alcDeviceResumeSOFT
- *
- * Resume the DSP to restart audio processing.
- */
-ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device)
-{
-    if(!VerifyDevice(&device) || device->Type != Playback)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else
-    {
-        almtx_lock(&device->BackendLock);
-        if((device->Flags&DEVICE_PAUSED))
-        {
-            device->Flags &= ~DEVICE_PAUSED;
-            if(ATOMIC_LOAD_SEQ(&device->ContextList) != NULL)
-            {
-                if(V0(device->Backend,start)() != ALC_FALSE)
-                    device->Flags |= DEVICE_RUNNING;
-                else
-                {
-                    V0(device->Backend,lock)();
-                    aluHandleDisconnect(device, "Device start failure");
-                    V0(device->Backend,unlock)();
-                    alcSetError(device, ALC_INVALID_DEVICE);
-                }
-            }
-        }
-        almtx_unlock(&device->BackendLock);
-    }
-    if(device) ALCdevice_DecRef(device);
-}
-
-
-/************************************************
- * ALC HRTF functions
- ************************************************/
-
-/* alcGetStringiSOFT
- *
- * Gets a string parameter at the given index.
- */
-ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index)
-{
-    const ALCchar *str = NULL;
-
-    if(!VerifyDevice(&device) || device->Type == Capture)
-        alcSetError(device, ALC_INVALID_DEVICE);
-    else switch(paramName)
-    {
-        case ALC_HRTF_SPECIFIER_SOFT:
-            if(index >= 0 && (size_t)index < VECTOR_SIZE(device->HrtfList))
-                str = alstr_get_cstr(VECTOR_ELEM(device->HrtfList, index).name);
-            else
-                alcSetError(device, ALC_INVALID_VALUE);
-            break;
-
-        default:
-            alcSetError(device, ALC_INVALID_ENUM);
-            break;
-    }
-    if(device) ALCdevice_DecRef(device);
-
-    return str;
-}
-
-/* alcResetDeviceSOFT
- *
- * Resets the given device output, using the specified attribute list.
- */
-ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs)
-{
-    ALCenum err;
-
-    LockLists();
-    if(!VerifyDevice(&device) || device->Type == Capture ||
-       !ATOMIC_LOAD(&device->Connected, almemory_order_relaxed))
-    {
-        UnlockLists();
-        alcSetError(device, ALC_INVALID_DEVICE);
-        if(device) ALCdevice_DecRef(device);
-        return ALC_FALSE;
-    }
-    almtx_lock(&device->BackendLock);
-    UnlockLists();
-
-    err = UpdateDeviceParams(device, attribs);
-    almtx_unlock(&device->BackendLock);
-
-    if(err != ALC_NO_ERROR)
-    {
-        alcSetError(device, err);
-        if(err == ALC_INVALID_DEVICE)
-        {
-            V0(device->Backend,lock)();
-            aluHandleDisconnect(device, "Device start failure");
-            V0(device->Backend,unlock)();
-        }
-        ALCdevice_DecRef(device);
-        return ALC_FALSE;
-    }
-    ALCdevice_DecRef(device);
-
-    return ALC_TRUE;
-}

+ 0 - 1927
Engine/lib/openal-soft/Alc/ALu.c

@@ -1,1927 +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 <math.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-
-#include "alMain.h"
-#include "alSource.h"
-#include "alBuffer.h"
-#include "alListener.h"
-#include "alAuxEffectSlot.h"
-#include "alu.h"
-#include "bs2b.h"
-#include "hrtf.h"
-#include "mastering.h"
-#include "uhjfilter.h"
-#include "bformatdec.h"
-#include "static_assert.h"
-#include "ringbuffer.h"
-#include "filters/splitter.h"
-
-#include "mixer/defs.h"
-#include "fpu_modes.h"
-#include "cpu_caps.h"
-#include "bsinc_inc.h"
-
-#include "backends/base.h"
-
-
-extern inline ALfloat minf(ALfloat a, ALfloat b);
-extern inline ALfloat maxf(ALfloat a, ALfloat b);
-extern inline ALfloat clampf(ALfloat val, ALfloat min, ALfloat max);
-
-extern inline ALdouble mind(ALdouble a, ALdouble b);
-extern inline ALdouble maxd(ALdouble a, ALdouble b);
-extern inline ALdouble clampd(ALdouble val, ALdouble min, ALdouble max);
-
-extern inline ALuint minu(ALuint a, ALuint b);
-extern inline ALuint maxu(ALuint a, ALuint b);
-extern inline ALuint clampu(ALuint val, ALuint min, ALuint max);
-
-extern inline ALint mini(ALint a, ALint b);
-extern inline ALint maxi(ALint a, ALint b);
-extern inline ALint clampi(ALint val, ALint min, ALint max);
-
-extern inline ALint64 mini64(ALint64 a, ALint64 b);
-extern inline ALint64 maxi64(ALint64 a, ALint64 b);
-extern inline ALint64 clampi64(ALint64 val, ALint64 min, ALint64 max);
-
-extern inline ALuint64 minu64(ALuint64 a, ALuint64 b);
-extern inline ALuint64 maxu64(ALuint64 a, ALuint64 b);
-extern inline ALuint64 clampu64(ALuint64 val, ALuint64 min, ALuint64 max);
-
-extern inline size_t minz(size_t a, size_t b);
-extern inline size_t maxz(size_t a, size_t b);
-extern inline size_t clampz(size_t val, size_t min, size_t max);
-
-extern inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu);
-extern inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu);
-
-extern inline void aluVectorSet(aluVector *restrict vector, ALfloat x, ALfloat y, ALfloat z, ALfloat w);
-
-extern inline void aluMatrixfSetRow(aluMatrixf *matrix, ALuint row,
-                                    ALfloat m0, ALfloat m1, ALfloat m2, ALfloat m3);
-extern inline void aluMatrixfSet(aluMatrixf *matrix,
-                                 ALfloat m00, ALfloat m01, ALfloat m02, ALfloat m03,
-                                 ALfloat m10, ALfloat m11, ALfloat m12, ALfloat m13,
-                                 ALfloat m20, ALfloat m21, ALfloat m22, ALfloat m23,
-                                 ALfloat m30, ALfloat m31, ALfloat m32, ALfloat m33);
-
-
-/* Cone scalar */
-ALfloat ConeScale = 1.0f;
-
-/* Localized Z scalar for mono sources */
-ALfloat ZScale = 1.0f;
-
-/* Force default speed of sound for distance-related reverb decay. */
-ALboolean OverrideReverbSpeedOfSound = AL_FALSE;
-
-const aluMatrixf IdentityMatrixf = {{
-    { 1.0f, 0.0f, 0.0f, 0.0f },
-    { 0.0f, 1.0f, 0.0f, 0.0f },
-    { 0.0f, 0.0f, 1.0f, 0.0f },
-    { 0.0f, 0.0f, 0.0f, 1.0f },
-}};
-
-
-static void ClearArray(ALfloat f[MAX_OUTPUT_CHANNELS])
-{
-    size_t i;
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        f[i] = 0.0f;
-}
-
-struct ChanMap {
-    enum Channel channel;
-    ALfloat angle;
-    ALfloat elevation;
-};
-
-static HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_C;
-
-
-void DeinitVoice(ALvoice *voice)
-{
-    al_free(ATOMIC_EXCHANGE_PTR_SEQ(&voice->Update, NULL));
-}
-
-
-static inline HrtfDirectMixerFunc SelectHrtfMixer(void)
-{
-#ifdef HAVE_NEON
-    if((CPUCapFlags&CPU_CAP_NEON))
-        return MixDirectHrtf_Neon;
-#endif
-#ifdef HAVE_SSE
-    if((CPUCapFlags&CPU_CAP_SSE))
-        return MixDirectHrtf_SSE;
-#endif
-
-    return MixDirectHrtf_C;
-}
-
-
-/* Prior to VS2013, MSVC lacks the round() family of functions. */
-#if defined(_MSC_VER) && _MSC_VER < 1800
-static float roundf(float val)
-{
-    if(val < 0.0f)
-        return ceilf(val-0.5f);
-    return floorf(val+0.5f);
-}
-#endif
-
-/* This RNG method was created based on the math found in opusdec. It's quick,
- * and starting with a seed value of 22222, is suitable for generating
- * whitenoise.
- */
-static inline ALuint dither_rng(ALuint *seed)
-{
-    *seed = (*seed * 96314165) + 907633515;
-    return *seed;
-}
-
-
-static inline void aluCrossproduct(const ALfloat *inVector1, const ALfloat *inVector2, ALfloat *outVector)
-{
-    outVector[0] = inVector1[1]*inVector2[2] - inVector1[2]*inVector2[1];
-    outVector[1] = inVector1[2]*inVector2[0] - inVector1[0]*inVector2[2];
-    outVector[2] = inVector1[0]*inVector2[1] - inVector1[1]*inVector2[0];
-}
-
-static inline ALfloat aluDotproduct(const aluVector *vec1, const aluVector *vec2)
-{
-    return vec1->v[0]*vec2->v[0] + vec1->v[1]*vec2->v[1] + vec1->v[2]*vec2->v[2];
-}
-
-static ALfloat aluNormalize(ALfloat *vec)
-{
-    ALfloat length = sqrtf(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
-    if(length > FLT_EPSILON)
-    {
-        ALfloat inv_length = 1.0f/length;
-        vec[0] *= inv_length;
-        vec[1] *= inv_length;
-        vec[2] *= inv_length;
-        return length;
-    }
-    vec[0] = vec[1] = vec[2] = 0.0f;
-    return 0.0f;
-}
-
-static void aluMatrixfFloat3(ALfloat *vec, ALfloat w, const aluMatrixf *mtx)
-{
-    ALfloat v[4] = { vec[0], vec[1], vec[2], w };
-
-    vec[0] = v[0]*mtx->m[0][0] + v[1]*mtx->m[1][0] + v[2]*mtx->m[2][0] + v[3]*mtx->m[3][0];
-    vec[1] = v[0]*mtx->m[0][1] + v[1]*mtx->m[1][1] + v[2]*mtx->m[2][1] + v[3]*mtx->m[3][1];
-    vec[2] = v[0]*mtx->m[0][2] + v[1]*mtx->m[1][2] + v[2]*mtx->m[2][2] + v[3]*mtx->m[3][2];
-}
-
-static aluVector aluMatrixfVector(const aluMatrixf *mtx, const aluVector *vec)
-{
-    aluVector v;
-    v.v[0] = vec->v[0]*mtx->m[0][0] + vec->v[1]*mtx->m[1][0] + vec->v[2]*mtx->m[2][0] + vec->v[3]*mtx->m[3][0];
-    v.v[1] = vec->v[0]*mtx->m[0][1] + vec->v[1]*mtx->m[1][1] + vec->v[2]*mtx->m[2][1] + vec->v[3]*mtx->m[3][1];
-    v.v[2] = vec->v[0]*mtx->m[0][2] + vec->v[1]*mtx->m[1][2] + vec->v[2]*mtx->m[2][2] + vec->v[3]*mtx->m[3][2];
-    v.v[3] = vec->v[0]*mtx->m[0][3] + vec->v[1]*mtx->m[1][3] + vec->v[2]*mtx->m[2][3] + vec->v[3]*mtx->m[3][3];
-    return v;
-}
-
-
-void aluInit(void)
-{
-    MixDirectHrtf = SelectHrtfMixer();
-}
-
-
-static void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
-{
-    ALbitfieldSOFT enabledevt;
-    AsyncEvent evt;
-    size_t strpos;
-    ALuint scale;
-
-    enabledevt = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
-    if(!(enabledevt&EventType_SourceStateChange)) return;
-
-    evt.EnumType = EventType_SourceStateChange;
-    evt.Type = AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT;
-    evt.ObjectId = id;
-    evt.Param = AL_STOPPED;
-
-    /* Normally snprintf would be used, but this is called from the mixer and
-     * that function's not real-time safe, so we have to construct it manually.
-     */
-    strcpy(evt.Message, "Source ID "); strpos = 10;
-    scale = 1000000000;
-    while(scale > 0 && scale > id)
-        scale /= 10;
-    while(scale > 0)
-    {
-        evt.Message[strpos++] = '0' + ((id/scale)%10);
-        scale /= 10;
-    }
-    strcpy(evt.Message+strpos, " state changed to AL_STOPPED");
-
-    if(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) == 1)
-        alsem_post(&context->EventSem);
-}
-
-
-static void ProcessHrtf(ALCdevice *device, ALsizei SamplesToDo)
-{
-    DirectHrtfState *state;
-    int lidx, ridx;
-    ALsizei c;
-
-    if(device->AmbiUp)
-        ambiup_process(device->AmbiUp,
-            device->Dry.Buffer, device->Dry.NumChannels, device->FOAOut.Buffer,
-            SamplesToDo
-        );
-
-    lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
-    ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
-    assert(lidx != -1 && ridx != -1);
-
-    state = device->Hrtf;
-    for(c = 0;c < device->Dry.NumChannels;c++)
-    {
-        MixDirectHrtf(device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
-            device->Dry.Buffer[c], state->Offset, state->IrSize,
-            state->Chan[c].Coeffs, state->Chan[c].Values, SamplesToDo
-        );
-    }
-    state->Offset += SamplesToDo;
-}
-
-static void ProcessAmbiDec(ALCdevice *device, ALsizei SamplesToDo)
-{
-    if(device->Dry.Buffer != device->FOAOut.Buffer)
-        bformatdec_upSample(device->AmbiDecoder,
-            device->Dry.Buffer, device->FOAOut.Buffer, device->FOAOut.NumChannels,
-            SamplesToDo
-        );
-    bformatdec_process(device->AmbiDecoder,
-        device->RealOut.Buffer, device->RealOut.NumChannels, device->Dry.Buffer,
-        SamplesToDo
-    );
-}
-
-static void ProcessAmbiUp(ALCdevice *device, ALsizei SamplesToDo)
-{
-    ambiup_process(device->AmbiUp,
-        device->RealOut.Buffer, device->RealOut.NumChannels, device->FOAOut.Buffer,
-        SamplesToDo
-    );
-}
-
-static void ProcessUhj(ALCdevice *device, ALsizei SamplesToDo)
-{
-    int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
-    int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
-    assert(lidx != -1 && ridx != -1);
-
-    /* Encode to stereo-compatible 2-channel UHJ output. */
-    EncodeUhj2(device->Uhj_Encoder,
-        device->RealOut.Buffer[lidx], device->RealOut.Buffer[ridx],
-        device->Dry.Buffer, SamplesToDo
-    );
-}
-
-static void ProcessBs2b(ALCdevice *device, ALsizei SamplesToDo)
-{
-    int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
-    int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
-    assert(lidx != -1 && ridx != -1);
-
-    /* Apply binaural/crossfeed filter */
-    bs2b_cross_feed(device->Bs2b, device->RealOut.Buffer[lidx],
-                    device->RealOut.Buffer[ridx], SamplesToDo);
-}
-
-void aluSelectPostProcess(ALCdevice *device)
-{
-    if(device->HrtfHandle)
-        device->PostProcess = ProcessHrtf;
-    else if(device->AmbiDecoder)
-        device->PostProcess = ProcessAmbiDec;
-    else if(device->AmbiUp)
-        device->PostProcess = ProcessAmbiUp;
-    else if(device->Uhj_Encoder)
-        device->PostProcess = ProcessUhj;
-    else if(device->Bs2b)
-        device->PostProcess = ProcessBs2b;
-    else
-        device->PostProcess = NULL;
-}
-
-
-/* Prepares the interpolator for a given rate (determined by increment).  A
- * result of AL_FALSE indicates that the filter output will completely cut
- * the input signal.
- *
- * With a bit of work, and a trade of memory for CPU cost, this could be
- * modified for use with an interpolated increment for buttery-smooth pitch
- * changes.
- */
-void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table)
-{
-    ALfloat sf = 0.0f;
-    ALsizei si = BSINC_SCALE_COUNT-1;
-
-    if(increment > FRACTIONONE)
-    {
-        sf = (ALfloat)FRACTIONONE / increment;
-        sf = maxf(0.0f, (BSINC_SCALE_COUNT-1) * (sf-table->scaleBase) * table->scaleRange);
-        si = float2int(sf);
-        /* The interpolation factor is fit to this diagonally-symmetric curve
-         * to reduce the transition ripple caused by interpolating different
-         * scales of the sinc function.
-         */
-        sf = 1.0f - cosf(asinf(sf - si));
-    }
-
-    state->sf = sf;
-    state->m = table->m[si];
-    state->l = -((state->m/2) - 1);
-    state->filter = table->Tab + table->filterOffset[si];
-}
-
-
-static bool CalcContextParams(ALCcontext *Context)
-{
-    ALlistener *Listener = Context->Listener;
-    struct ALcontextProps *props;
-
-    props = ATOMIC_EXCHANGE_PTR(&Context->Update, NULL, almemory_order_acq_rel);
-    if(!props) return false;
-
-    Listener->Params.MetersPerUnit = props->MetersPerUnit;
-
-    Listener->Params.DopplerFactor = props->DopplerFactor;
-    Listener->Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
-    if(!OverrideReverbSpeedOfSound)
-        Listener->Params.ReverbSpeedOfSound = Listener->Params.SpeedOfSound *
-                                              Listener->Params.MetersPerUnit;
-
-    Listener->Params.SourceDistanceModel = props->SourceDistanceModel;
-    Listener->Params.DistanceModel = props->DistanceModel;
-
-    ATOMIC_REPLACE_HEAD(struct ALcontextProps*, &Context->FreeContextProps, props);
-    return true;
-}
-
-static bool CalcListenerParams(ALCcontext *Context)
-{
-    ALlistener *Listener = Context->Listener;
-    ALfloat N[3], V[3], U[3], P[3];
-    struct ALlistenerProps *props;
-    aluVector vel;
-
-    props = ATOMIC_EXCHANGE_PTR(&Listener->Update, NULL, almemory_order_acq_rel);
-    if(!props) return false;
-
-    /* AT then UP */
-    N[0] = props->Forward[0];
-    N[1] = props->Forward[1];
-    N[2] = props->Forward[2];
-    aluNormalize(N);
-    V[0] = props->Up[0];
-    V[1] = props->Up[1];
-    V[2] = props->Up[2];
-    aluNormalize(V);
-    /* Build and normalize right-vector */
-    aluCrossproduct(N, V, U);
-    aluNormalize(U);
-
-    aluMatrixfSet(&Listener->Params.Matrix,
-        U[0], V[0], -N[0], 0.0,
-        U[1], V[1], -N[1], 0.0,
-        U[2], V[2], -N[2], 0.0,
-         0.0,  0.0,   0.0, 1.0
-    );
-
-    P[0] = props->Position[0];
-    P[1] = props->Position[1];
-    P[2] = props->Position[2];
-    aluMatrixfFloat3(P, 1.0, &Listener->Params.Matrix);
-    aluMatrixfSetRow(&Listener->Params.Matrix, 3, -P[0], -P[1], -P[2], 1.0f);
-
-    aluVectorSet(&vel, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
-    Listener->Params.Velocity = aluMatrixfVector(&Listener->Params.Matrix, &vel);
-
-    Listener->Params.Gain = props->Gain * Context->GainBoost;
-
-    ATOMIC_REPLACE_HEAD(struct ALlistenerProps*, &Context->FreeListenerProps, props);
-    return true;
-}
-
-static bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context, bool force)
-{
-    struct ALeffectslotProps *props;
-    ALeffectState *state;
-
-    props = ATOMIC_EXCHANGE_PTR(&slot->Update, NULL, almemory_order_acq_rel);
-    if(!props && !force) return false;
-
-    if(props)
-    {
-        slot->Params.Gain = props->Gain;
-        slot->Params.AuxSendAuto = props->AuxSendAuto;
-        slot->Params.EffectType = props->Type;
-        slot->Params.EffectProps = props->Props;
-        if(IsReverbEffect(props->Type))
-        {
-            slot->Params.RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
-            slot->Params.DecayTime = props->Props.Reverb.DecayTime;
-            slot->Params.DecayLFRatio = props->Props.Reverb.DecayLFRatio;
-            slot->Params.DecayHFRatio = props->Props.Reverb.DecayHFRatio;
-            slot->Params.DecayHFLimit = props->Props.Reverb.DecayHFLimit;
-            slot->Params.AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
-        }
-        else
-        {
-            slot->Params.RoomRolloff = 0.0f;
-            slot->Params.DecayTime = 0.0f;
-            slot->Params.DecayLFRatio = 0.0f;
-            slot->Params.DecayHFRatio = 0.0f;
-            slot->Params.DecayHFLimit = AL_FALSE;
-            slot->Params.AirAbsorptionGainHF = 1.0f;
-        }
-
-        /* Swap effect states. No need to play with the ref counts since they
-         * keep the same number of refs.
-         */
-        state = props->State;
-        props->State = slot->Params.EffectState;
-        slot->Params.EffectState = state;
-
-        ATOMIC_REPLACE_HEAD(struct ALeffectslotProps*, &context->FreeEffectslotProps, props);
-    }
-    else
-        state = slot->Params.EffectState;
-
-    V(state,update)(context, slot, &slot->Params.EffectProps);
-    return true;
-}
-
-
-static const struct ChanMap MonoMap[1] = {
-    { FrontCenter, 0.0f, 0.0f }
-}, RearMap[2] = {
-    { BackLeft,  DEG2RAD(-150.0f), DEG2RAD(0.0f) },
-    { BackRight, DEG2RAD( 150.0f), DEG2RAD(0.0f) }
-}, QuadMap[4] = {
-    { FrontLeft,  DEG2RAD( -45.0f), DEG2RAD(0.0f) },
-    { FrontRight, DEG2RAD(  45.0f), DEG2RAD(0.0f) },
-    { BackLeft,   DEG2RAD(-135.0f), DEG2RAD(0.0f) },
-    { BackRight,  DEG2RAD( 135.0f), DEG2RAD(0.0f) }
-}, X51Map[6] = {
-    { FrontLeft,   DEG2RAD( -30.0f), DEG2RAD(0.0f) },
-    { FrontRight,  DEG2RAD(  30.0f), DEG2RAD(0.0f) },
-    { FrontCenter, DEG2RAD(   0.0f), DEG2RAD(0.0f) },
-    { LFE, 0.0f, 0.0f },
-    { SideLeft,    DEG2RAD(-110.0f), DEG2RAD(0.0f) },
-    { SideRight,   DEG2RAD( 110.0f), DEG2RAD(0.0f) }
-}, X61Map[7] = {
-    { FrontLeft,    DEG2RAD(-30.0f), DEG2RAD(0.0f) },
-    { FrontRight,   DEG2RAD( 30.0f), DEG2RAD(0.0f) },
-    { FrontCenter,  DEG2RAD(  0.0f), DEG2RAD(0.0f) },
-    { LFE, 0.0f, 0.0f },
-    { BackCenter,   DEG2RAD(180.0f), DEG2RAD(0.0f) },
-    { SideLeft,     DEG2RAD(-90.0f), DEG2RAD(0.0f) },
-    { SideRight,    DEG2RAD( 90.0f), DEG2RAD(0.0f) }
-}, X71Map[8] = {
-    { FrontLeft,   DEG2RAD( -30.0f), DEG2RAD(0.0f) },
-    { FrontRight,  DEG2RAD(  30.0f), DEG2RAD(0.0f) },
-    { FrontCenter, DEG2RAD(   0.0f), DEG2RAD(0.0f) },
-    { LFE, 0.0f, 0.0f },
-    { BackLeft,    DEG2RAD(-150.0f), DEG2RAD(0.0f) },
-    { BackRight,   DEG2RAD( 150.0f), DEG2RAD(0.0f) },
-    { SideLeft,    DEG2RAD( -90.0f), DEG2RAD(0.0f) },
-    { SideRight,   DEG2RAD(  90.0f), DEG2RAD(0.0f) }
-};
-
-static void CalcPanningAndFilters(ALvoice *voice, const ALfloat Azi, const ALfloat Elev,
-                                  const ALfloat Distance, const ALfloat Spread,
-                                  const ALfloat DryGain, const ALfloat DryGainHF,
-                                  const ALfloat DryGainLF, const ALfloat *WetGain,
-                                  const ALfloat *WetGainLF, const ALfloat *WetGainHF,
-                                  ALeffectslot **SendSlots, const ALbuffer *Buffer,
-                                  const struct ALvoiceProps *props, const ALlistener *Listener,
-                                  const ALCdevice *Device)
-{
-    struct ChanMap StereoMap[2] = {
-        { FrontLeft,  DEG2RAD(-30.0f), DEG2RAD(0.0f) },
-        { FrontRight, DEG2RAD( 30.0f), DEG2RAD(0.0f) }
-    };
-    bool DirectChannels = props->DirectChannels;
-    const ALsizei NumSends = Device->NumAuxSends;
-    const ALuint Frequency = Device->Frequency;
-    const struct ChanMap *chans = NULL;
-    ALsizei num_channels = 0;
-    bool isbformat = false;
-    ALfloat downmix_gain = 1.0f;
-    ALsizei c, i;
-
-    switch(Buffer->FmtChannels)
-    {
-    case FmtMono:
-        chans = MonoMap;
-        num_channels = 1;
-        /* Mono buffers are never played direct. */
-        DirectChannels = false;
-        break;
-
-    case FmtStereo:
-        /* Convert counter-clockwise to clockwise. */
-        StereoMap[0].angle = -props->StereoPan[0];
-        StereoMap[1].angle = -props->StereoPan[1];
-
-        chans = StereoMap;
-        num_channels = 2;
-        downmix_gain = 1.0f / 2.0f;
-        break;
-
-    case FmtRear:
-        chans = RearMap;
-        num_channels = 2;
-        downmix_gain = 1.0f / 2.0f;
-        break;
-
-    case FmtQuad:
-        chans = QuadMap;
-        num_channels = 4;
-        downmix_gain = 1.0f / 4.0f;
-        break;
-
-    case FmtX51:
-        chans = X51Map;
-        num_channels = 6;
-        /* NOTE: Excludes LFE. */
-        downmix_gain = 1.0f / 5.0f;
-        break;
-
-    case FmtX61:
-        chans = X61Map;
-        num_channels = 7;
-        /* NOTE: Excludes LFE. */
-        downmix_gain = 1.0f / 6.0f;
-        break;
-
-    case FmtX71:
-        chans = X71Map;
-        num_channels = 8;
-        /* NOTE: Excludes LFE. */
-        downmix_gain = 1.0f / 7.0f;
-        break;
-
-    case FmtBFormat2D:
-        num_channels = 3;
-        isbformat = true;
-        DirectChannels = false;
-        break;
-
-    case FmtBFormat3D:
-        num_channels = 4;
-        isbformat = true;
-        DirectChannels = false;
-        break;
-    }
-
-    for(c = 0;c < num_channels;c++)
-    {
-        memset(&voice->Direct.Params[c].Hrtf.Target, 0,
-               sizeof(voice->Direct.Params[c].Hrtf.Target));
-        ClearArray(voice->Direct.Params[c].Gains.Target);
-    }
-    for(i = 0;i < NumSends;i++)
-    {
-        for(c = 0;c < num_channels;c++)
-            ClearArray(voice->Send[i].Params[c].Gains.Target);
-    }
-
-    voice->Flags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
-    if(isbformat)
-    {
-        /* Special handling for B-Format sources. */
-
-        if(Distance > FLT_EPSILON)
-        {
-            /* Panning a B-Format sound toward some direction is easy. Just pan
-             * the first (W) channel as a normal mono sound and silence the
-             * others.
-             */
-            ALfloat coeffs[MAX_AMBI_COEFFS];
-
-            if(Device->AvgSpeakerDist > 0.0f)
-            {
-                ALfloat mdist = Distance * Listener->Params.MetersPerUnit;
-                ALfloat w0 = SPEEDOFSOUNDMETRESPERSEC /
-                             (mdist * (ALfloat)Device->Frequency);
-                ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
-                             (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
-                /* Clamp w0 for really close distances, to prevent excessive
-                 * bass.
-                 */
-                w0 = minf(w0, w1*4.0f);
-
-                /* Only need to adjust the first channel of a B-Format source. */
-                NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, w0);
-
-                for(i = 0;i < MAX_AMBI_ORDER+1;i++)
-                    voice->Direct.ChannelsPerOrder[i] = Device->Dry.NumChannelsPerOrder[i];
-                voice->Flags |= VOICE_HAS_NFC;
-            }
-
-            if(Device->Render_Mode == StereoPair)
-                CalcAnglePairwiseCoeffs(Azi, Elev, Spread, coeffs);
-            else
-                CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
-
-            /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
-            ComputeDryPanGains(&Device->Dry, coeffs, DryGain*1.414213562f,
-                               voice->Direct.Params[0].Gains.Target);
-            for(i = 0;i < NumSends;i++)
-            {
-                const ALeffectslot *Slot = SendSlots[i];
-                if(Slot)
-                    ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
-                        coeffs, WetGain[i]*1.414213562f, voice->Send[i].Params[0].Gains.Target
-                    );
-            }
-        }
-        else
-        {
-            /* Local B-Format sources have their XYZ channels rotated according
-             * to the orientation.
-             */
-            const ALfloat sqrt_2 = sqrtf(2.0f);
-            const ALfloat sqrt_3 = sqrtf(3.0f);
-            ALfloat N[3], V[3], U[3];
-            aluMatrixf matrix;
-
-            if(Device->AvgSpeakerDist > 0.0f)
-            {
-                /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
-                 * is what we want for FOA input. The first channel may have
-                 * been previously re-adjusted if panned, so reset it.
-                 */
-                NfcFilterAdjust(&voice->Direct.Params[0].NFCtrlFilter, 0.0f);
-
-                voice->Direct.ChannelsPerOrder[0] = 1;
-                voice->Direct.ChannelsPerOrder[1] = mini(voice->Direct.Channels-1, 3);
-                for(i = 2;i < MAX_AMBI_ORDER+1;i++)
-                    voice->Direct.ChannelsPerOrder[i] = 0;
-                voice->Flags |= VOICE_HAS_NFC;
-            }
-
-            /* AT then UP */
-            N[0] = props->Orientation[0][0];
-            N[1] = props->Orientation[0][1];
-            N[2] = props->Orientation[0][2];
-            aluNormalize(N);
-            V[0] = props->Orientation[1][0];
-            V[1] = props->Orientation[1][1];
-            V[2] = props->Orientation[1][2];
-            aluNormalize(V);
-            if(!props->HeadRelative)
-            {
-                const aluMatrixf *lmatrix = &Listener->Params.Matrix;
-                aluMatrixfFloat3(N, 0.0f, lmatrix);
-                aluMatrixfFloat3(V, 0.0f, lmatrix);
-            }
-            /* Build and normalize right-vector */
-            aluCrossproduct(N, V, U);
-            aluNormalize(U);
-
-            /* Build a rotate + conversion matrix (FuMa -> ACN+N3D). NOTE: This
-             * matrix is transposed, for the inputs to align on the rows and
-             * outputs on the columns.
-             */
-            aluMatrixfSet(&matrix,
-                // ACN0         ACN1          ACN2          ACN3
-                sqrt_2,         0.0f,         0.0f,         0.0f, // Ambi W
-                  0.0f, -N[0]*sqrt_3,  N[1]*sqrt_3, -N[2]*sqrt_3, // Ambi X
-                  0.0f,  U[0]*sqrt_3, -U[1]*sqrt_3,  U[2]*sqrt_3, // Ambi Y
-                  0.0f, -V[0]*sqrt_3,  V[1]*sqrt_3, -V[2]*sqrt_3  // Ambi Z
-            );
-
-            voice->Direct.Buffer = Device->FOAOut.Buffer;
-            voice->Direct.Channels = Device->FOAOut.NumChannels;
-            for(c = 0;c < num_channels;c++)
-                ComputeFirstOrderGains(&Device->FOAOut, matrix.m[c], DryGain,
-                                       voice->Direct.Params[c].Gains.Target);
-            for(i = 0;i < NumSends;i++)
-            {
-                const ALeffectslot *Slot = SendSlots[i];
-                if(Slot)
-                {
-                    for(c = 0;c < num_channels;c++)
-                        ComputeFirstOrderGainsBF(Slot->ChanMap, Slot->NumChannels,
-                            matrix.m[c], WetGain[i], voice->Send[i].Params[c].Gains.Target
-                        );
-                }
-            }
-        }
-    }
-    else if(DirectChannels)
-    {
-        /* Direct source channels always play local. Skip the virtual channels
-         * and write inputs to the matching real outputs.
-         */
-        voice->Direct.Buffer = Device->RealOut.Buffer;
-        voice->Direct.Channels = Device->RealOut.NumChannels;
-
-        for(c = 0;c < num_channels;c++)
-        {
-            int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
-            if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
-        }
-
-        /* Auxiliary sends still use normal channel panning since they mix to
-         * B-Format, which can't channel-match.
-         */
-        for(c = 0;c < num_channels;c++)
-        {
-            ALfloat coeffs[MAX_AMBI_COEFFS];
-            CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
-
-            for(i = 0;i < NumSends;i++)
-            {
-                const ALeffectslot *Slot = SendSlots[i];
-                if(Slot)
-                    ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
-                        coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
-                    );
-            }
-        }
-    }
-    else if(Device->Render_Mode == HrtfRender)
-    {
-        /* Full HRTF rendering. Skip the virtual channels and render to the
-         * real outputs.
-         */
-        voice->Direct.Buffer = Device->RealOut.Buffer;
-        voice->Direct.Channels = Device->RealOut.NumChannels;
-
-        if(Distance > FLT_EPSILON)
-        {
-            ALfloat coeffs[MAX_AMBI_COEFFS];
-
-            /* Get the HRIR coefficients and delays just once, for the given
-             * source direction.
-             */
-            GetHrtfCoeffs(Device->HrtfHandle, Elev, Azi, Spread,
-                          voice->Direct.Params[0].Hrtf.Target.Coeffs,
-                          voice->Direct.Params[0].Hrtf.Target.Delay);
-            voice->Direct.Params[0].Hrtf.Target.Gain = DryGain * downmix_gain;
-
-            /* Remaining channels use the same results as the first. */
-            for(c = 1;c < num_channels;c++)
-            {
-                /* Skip LFE */
-                if(chans[c].channel != LFE)
-                    voice->Direct.Params[c].Hrtf.Target = voice->Direct.Params[0].Hrtf.Target;
-            }
-
-            /* Calculate the directional coefficients once, which apply to all
-             * input channels of the source sends.
-             */
-            CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
-
-            for(i = 0;i < NumSends;i++)
-            {
-                const ALeffectslot *Slot = SendSlots[i];
-                if(Slot)
-                    for(c = 0;c < num_channels;c++)
-                    {
-                        /* Skip LFE */
-                        if(chans[c].channel != LFE)
-                            ComputePanningGainsBF(Slot->ChanMap,
-                                Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
-                                voice->Send[i].Params[c].Gains.Target
-                            );
-                    }
-            }
-        }
-        else
-        {
-            /* Local sources on HRTF play with each channel panned to its
-             * relative location around the listener, providing "virtual
-             * speaker" responses.
-             */
-            for(c = 0;c < num_channels;c++)
-            {
-                ALfloat coeffs[MAX_AMBI_COEFFS];
-
-                if(chans[c].channel == LFE)
-                {
-                    /* Skip LFE */
-                    continue;
-                }
-
-                /* Get the HRIR coefficients and delays for this channel
-                 * position.
-                 */
-                GetHrtfCoeffs(Device->HrtfHandle,
-                    chans[c].elevation, chans[c].angle, Spread,
-                    voice->Direct.Params[c].Hrtf.Target.Coeffs,
-                    voice->Direct.Params[c].Hrtf.Target.Delay
-                );
-                voice->Direct.Params[c].Hrtf.Target.Gain = DryGain;
-
-                /* Normal panning for auxiliary sends. */
-                CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
-
-                for(i = 0;i < NumSends;i++)
-                {
-                    const ALeffectslot *Slot = SendSlots[i];
-                    if(Slot)
-                        ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
-                            coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
-                        );
-                }
-            }
-        }
-
-        voice->Flags |= VOICE_HAS_HRTF;
-    }
-    else
-    {
-        /* Non-HRTF rendering. Use normal panning to the output. */
-
-        if(Distance > FLT_EPSILON)
-        {
-            ALfloat coeffs[MAX_AMBI_COEFFS];
-            ALfloat w0 = 0.0f;
-
-            /* Calculate NFC filter coefficient if needed. */
-            if(Device->AvgSpeakerDist > 0.0f)
-            {
-                ALfloat mdist = Distance * Listener->Params.MetersPerUnit;
-                ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
-                             (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
-                w0 = SPEEDOFSOUNDMETRESPERSEC /
-                     (mdist * (ALfloat)Device->Frequency);
-                /* Clamp w0 for really close distances, to prevent excessive
-                 * bass.
-                 */
-                w0 = minf(w0, w1*4.0f);
-
-                /* Adjust NFC filters. */
-                for(c = 0;c < num_channels;c++)
-                    NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
-
-                for(i = 0;i < MAX_AMBI_ORDER+1;i++)
-                    voice->Direct.ChannelsPerOrder[i] = Device->Dry.NumChannelsPerOrder[i];
-                voice->Flags |= VOICE_HAS_NFC;
-            }
-
-            /* Calculate the directional coefficients once, which apply to all
-             * input channels.
-             */
-            if(Device->Render_Mode == StereoPair)
-                CalcAnglePairwiseCoeffs(Azi, Elev, Spread, coeffs);
-            else
-                CalcAngleCoeffs(Azi, Elev, Spread, coeffs);
-
-            for(c = 0;c < num_channels;c++)
-            {
-                /* Special-case LFE */
-                if(chans[c].channel == LFE)
-                {
-                    if(Device->Dry.Buffer == Device->RealOut.Buffer)
-                    {
-                        int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
-                        if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
-                    }
-                    continue;
-                }
-
-                ComputeDryPanGains(&Device->Dry,
-                    coeffs, DryGain * downmix_gain, voice->Direct.Params[c].Gains.Target
-                );
-            }
-
-            for(i = 0;i < NumSends;i++)
-            {
-                const ALeffectslot *Slot = SendSlots[i];
-                if(Slot)
-                    for(c = 0;c < num_channels;c++)
-                    {
-                        /* Skip LFE */
-                        if(chans[c].channel != LFE)
-                            ComputePanningGainsBF(Slot->ChanMap,
-                                Slot->NumChannels, coeffs, WetGain[i] * downmix_gain,
-                                voice->Send[i].Params[c].Gains.Target
-                            );
-                    }
-            }
-        }
-        else
-        {
-            ALfloat w0 = 0.0f;
-
-            if(Device->AvgSpeakerDist > 0.0f)
-            {
-                /* If the source distance is 0, set w0 to w1 to act as a pass-
-                 * through. We still want to pass the signal through the
-                 * filters so they keep an appropriate history, in case the
-                 * source moves away from the listener.
-                 */
-                w0 = SPEEDOFSOUNDMETRESPERSEC /
-                     (Device->AvgSpeakerDist * (ALfloat)Device->Frequency);
-
-                for(c = 0;c < num_channels;c++)
-                    NfcFilterAdjust(&voice->Direct.Params[c].NFCtrlFilter, w0);
-
-                for(i = 0;i < MAX_AMBI_ORDER+1;i++)
-                    voice->Direct.ChannelsPerOrder[i] = Device->Dry.NumChannelsPerOrder[i];
-                voice->Flags |= VOICE_HAS_NFC;
-            }
-
-            for(c = 0;c < num_channels;c++)
-            {
-                ALfloat coeffs[MAX_AMBI_COEFFS];
-
-                /* Special-case LFE */
-                if(chans[c].channel == LFE)
-                {
-                    if(Device->Dry.Buffer == Device->RealOut.Buffer)
-                    {
-                        int idx = GetChannelIdxByName(&Device->RealOut, chans[c].channel);
-                        if(idx != -1) voice->Direct.Params[c].Gains.Target[idx] = DryGain;
-                    }
-                    continue;
-                }
-
-                if(Device->Render_Mode == StereoPair)
-                    CalcAnglePairwiseCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
-                else
-                    CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
-                ComputeDryPanGains(&Device->Dry,
-                    coeffs, DryGain, voice->Direct.Params[c].Gains.Target
-                );
-
-                for(i = 0;i < NumSends;i++)
-                {
-                    const ALeffectslot *Slot = SendSlots[i];
-                    if(Slot)
-                        ComputePanningGainsBF(Slot->ChanMap, Slot->NumChannels,
-                            coeffs, WetGain[i], voice->Send[i].Params[c].Gains.Target
-                        );
-                }
-            }
-        }
-    }
-
-    {
-        ALfloat hfScale = props->Direct.HFReference / Frequency;
-        ALfloat lfScale = props->Direct.LFReference / Frequency;
-        ALfloat gainHF = maxf(DryGainHF, 0.001f); /* Limit -60dB */
-        ALfloat gainLF = maxf(DryGainLF, 0.001f);
-
-        voice->Direct.FilterType = AF_None;
-        if(gainHF != 1.0f) voice->Direct.FilterType |= AF_LowPass;
-        if(gainLF != 1.0f) voice->Direct.FilterType |= AF_HighPass;
-        BiquadFilter_setParams(
-            &voice->Direct.Params[0].LowPass, BiquadType_HighShelf,
-            gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
-        );
-        BiquadFilter_setParams(
-            &voice->Direct.Params[0].HighPass, BiquadType_LowShelf,
-            gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
-        );
-        for(c = 1;c < num_channels;c++)
-        {
-            BiquadFilter_copyParams(&voice->Direct.Params[c].LowPass,
-                                    &voice->Direct.Params[0].LowPass);
-            BiquadFilter_copyParams(&voice->Direct.Params[c].HighPass,
-                                    &voice->Direct.Params[0].HighPass);
-        }
-    }
-    for(i = 0;i < NumSends;i++)
-    {
-        ALfloat hfScale = props->Send[i].HFReference / Frequency;
-        ALfloat lfScale = props->Send[i].LFReference / Frequency;
-        ALfloat gainHF = maxf(WetGainHF[i], 0.001f);
-        ALfloat gainLF = maxf(WetGainLF[i], 0.001f);
-
-        voice->Send[i].FilterType = AF_None;
-        if(gainHF != 1.0f) voice->Send[i].FilterType |= AF_LowPass;
-        if(gainLF != 1.0f) voice->Send[i].FilterType |= AF_HighPass;
-        BiquadFilter_setParams(
-            &voice->Send[i].Params[0].LowPass, BiquadType_HighShelf,
-            gainHF, hfScale, calc_rcpQ_from_slope(gainHF, 1.0f)
-        );
-        BiquadFilter_setParams(
-            &voice->Send[i].Params[0].HighPass, BiquadType_LowShelf,
-            gainLF, lfScale, calc_rcpQ_from_slope(gainLF, 1.0f)
-        );
-        for(c = 1;c < num_channels;c++)
-        {
-            BiquadFilter_copyParams(&voice->Send[i].Params[c].LowPass,
-                                    &voice->Send[i].Params[0].LowPass);
-            BiquadFilter_copyParams(&voice->Send[i].Params[c].HighPass,
-                                    &voice->Send[i].Params[0].HighPass);
-        }
-    }
-}
-
-static void CalcNonAttnSourceParams(ALvoice *voice, const struct ALvoiceProps *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
-{
-    const ALCdevice *Device = ALContext->Device;
-    const ALlistener *Listener = ALContext->Listener;
-    ALfloat DryGain, DryGainHF, DryGainLF;
-    ALfloat WetGain[MAX_SENDS];
-    ALfloat WetGainHF[MAX_SENDS];
-    ALfloat WetGainLF[MAX_SENDS];
-    ALeffectslot *SendSlots[MAX_SENDS];
-    ALfloat Pitch;
-    ALsizei i;
-
-    voice->Direct.Buffer = Device->Dry.Buffer;
-    voice->Direct.Channels = Device->Dry.NumChannels;
-    for(i = 0;i < Device->NumAuxSends;i++)
-    {
-        SendSlots[i] = props->Send[i].Slot;
-        if(!SendSlots[i] && i == 0)
-            SendSlots[i] = ALContext->DefaultSlot;
-        if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
-        {
-            SendSlots[i] = NULL;
-            voice->Send[i].Buffer = NULL;
-            voice->Send[i].Channels = 0;
-        }
-        else
-        {
-            voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
-            voice->Send[i].Channels = SendSlots[i]->NumChannels;
-        }
-    }
-
-    /* Calculate the stepping value */
-    Pitch = (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency * props->Pitch;
-    if(Pitch > (ALfloat)MAX_PITCH)
-        voice->Step = MAX_PITCH<<FRACTIONBITS;
-    else
-        voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
-    if(props->Resampler == BSinc24Resampler)
-        BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
-    else if(props->Resampler == BSinc12Resampler)
-        BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
-    voice->Resampler = SelectResampler(props->Resampler);
-
-    /* Calculate gains */
-    DryGain  = clampf(props->Gain, props->MinGain, props->MaxGain);
-    DryGain *= props->Direct.Gain * Listener->Params.Gain;
-    DryGain  = minf(DryGain, GAIN_MIX_MAX);
-    DryGainHF = props->Direct.GainHF;
-    DryGainLF = props->Direct.GainLF;
-    for(i = 0;i < Device->NumAuxSends;i++)
-    {
-        WetGain[i]  = clampf(props->Gain, props->MinGain, props->MaxGain);
-        WetGain[i] *= props->Send[i].Gain * Listener->Params.Gain;
-        WetGain[i]  = minf(WetGain[i], GAIN_MIX_MAX);
-        WetGainHF[i] = props->Send[i].GainHF;
-        WetGainLF[i] = props->Send[i].GainLF;
-    }
-
-    CalcPanningAndFilters(voice, 0.0f, 0.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF, WetGain,
-                          WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
-}
-
-static void CalcAttnSourceParams(ALvoice *voice, const struct ALvoiceProps *props, const ALbuffer *ALBuffer, const ALCcontext *ALContext)
-{
-    const ALCdevice *Device = ALContext->Device;
-    const ALlistener *Listener = ALContext->Listener;
-    const ALsizei NumSends = Device->NumAuxSends;
-    aluVector Position, Velocity, Direction, SourceToListener;
-    ALfloat Distance, ClampedDist, DopplerFactor;
-    ALeffectslot *SendSlots[MAX_SENDS];
-    ALfloat RoomRolloff[MAX_SENDS];
-    ALfloat DecayDistance[MAX_SENDS];
-    ALfloat DecayLFDistance[MAX_SENDS];
-    ALfloat DecayHFDistance[MAX_SENDS];
-    ALfloat DryGain, DryGainHF, DryGainLF;
-    ALfloat WetGain[MAX_SENDS];
-    ALfloat WetGainHF[MAX_SENDS];
-    ALfloat WetGainLF[MAX_SENDS];
-    bool directional;
-    ALfloat ev, az;
-    ALfloat spread;
-    ALfloat Pitch;
-    ALint i;
-
-    /* Set mixing buffers and get send parameters. */
-    voice->Direct.Buffer = Device->Dry.Buffer;
-    voice->Direct.Channels = Device->Dry.NumChannels;
-    for(i = 0;i < NumSends;i++)
-    {
-        SendSlots[i] = props->Send[i].Slot;
-        if(!SendSlots[i] && i == 0)
-            SendSlots[i] = ALContext->DefaultSlot;
-        if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
-        {
-            SendSlots[i] = NULL;
-            RoomRolloff[i] = 0.0f;
-            DecayDistance[i] = 0.0f;
-            DecayLFDistance[i] = 0.0f;
-            DecayHFDistance[i] = 0.0f;
-        }
-        else if(SendSlots[i]->Params.AuxSendAuto)
-        {
-            RoomRolloff[i] = SendSlots[i]->Params.RoomRolloff + props->RoomRolloffFactor;
-            /* Calculate the distances to where this effect's decay reaches
-             * -60dB.
-             */
-            DecayDistance[i] = SendSlots[i]->Params.DecayTime *
-                               Listener->Params.ReverbSpeedOfSound;
-            DecayLFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayLFRatio;
-            DecayHFDistance[i] = DecayDistance[i] * SendSlots[i]->Params.DecayHFRatio;
-            if(SendSlots[i]->Params.DecayHFLimit)
-            {
-                ALfloat airAbsorption = SendSlots[i]->Params.AirAbsorptionGainHF;
-                if(airAbsorption < 1.0f)
-                {
-                    /* Calculate the distance to where this effect's air
-                     * absorption reaches -60dB, and limit the effect's HF
-                     * decay distance (so it doesn't take any longer to decay
-                     * than the air would allow).
-                     */
-                    ALfloat absorb_dist = log10f(REVERB_DECAY_GAIN) / log10f(airAbsorption);
-                    DecayHFDistance[i] = minf(absorb_dist, DecayHFDistance[i]);
-                }
-            }
-        }
-        else
-        {
-            /* If the slot's auxiliary send auto is off, the data sent to the
-             * effect slot is the same as the dry path, sans filter effects */
-            RoomRolloff[i] = props->RolloffFactor;
-            DecayDistance[i] = 0.0f;
-            DecayLFDistance[i] = 0.0f;
-            DecayHFDistance[i] = 0.0f;
-        }
-
-        if(!SendSlots[i])
-        {
-            voice->Send[i].Buffer = NULL;
-            voice->Send[i].Channels = 0;
-        }
-        else
-        {
-            voice->Send[i].Buffer = SendSlots[i]->WetBuffer;
-            voice->Send[i].Channels = SendSlots[i]->NumChannels;
-        }
-    }
-
-    /* Transform source to listener space (convert to head relative) */
-    aluVectorSet(&Position, props->Position[0], props->Position[1], props->Position[2], 1.0f);
-    aluVectorSet(&Direction, props->Direction[0], props->Direction[1], props->Direction[2], 0.0f);
-    aluVectorSet(&Velocity, props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f);
-    if(props->HeadRelative == AL_FALSE)
-    {
-        const aluMatrixf *Matrix = &Listener->Params.Matrix;
-        /* Transform source vectors */
-        Position = aluMatrixfVector(Matrix, &Position);
-        Velocity = aluMatrixfVector(Matrix, &Velocity);
-        Direction = aluMatrixfVector(Matrix, &Direction);
-    }
-    else
-    {
-        const aluVector *lvelocity = &Listener->Params.Velocity;
-        /* Offset the source velocity to be relative of the listener velocity */
-        Velocity.v[0] += lvelocity->v[0];
-        Velocity.v[1] += lvelocity->v[1];
-        Velocity.v[2] += lvelocity->v[2];
-    }
-
-    directional = aluNormalize(Direction.v) > 0.0f;
-    SourceToListener.v[0] = -Position.v[0];
-    SourceToListener.v[1] = -Position.v[1];
-    SourceToListener.v[2] = -Position.v[2];
-    SourceToListener.v[3] = 0.0f;
-    Distance = aluNormalize(SourceToListener.v);
-
-    /* Initial source gain */
-    DryGain = props->Gain;
-    DryGainHF = 1.0f;
-    DryGainLF = 1.0f;
-    for(i = 0;i < NumSends;i++)
-    {
-        WetGain[i] = props->Gain;
-        WetGainHF[i] = 1.0f;
-        WetGainLF[i] = 1.0f;
-    }
-
-    /* Calculate distance attenuation */
-    ClampedDist = Distance;
-
-    switch(Listener->Params.SourceDistanceModel ?
-           props->DistanceModel : Listener->Params.DistanceModel)
-    {
-        case InverseDistanceClamped:
-            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
-            if(props->MaxDistance < props->RefDistance)
-                break;
-            /*fall-through*/
-        case InverseDistance:
-            if(!(props->RefDistance > 0.0f))
-                ClampedDist = props->RefDistance;
-            else
-            {
-                ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
-                if(dist > 0.0f) DryGain *= props->RefDistance / dist;
-                for(i = 0;i < NumSends;i++)
-                {
-                    dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
-                    if(dist > 0.0f) WetGain[i] *= props->RefDistance / dist;
-                }
-            }
-            break;
-
-        case LinearDistanceClamped:
-            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
-            if(props->MaxDistance < props->RefDistance)
-                break;
-            /*fall-through*/
-        case LinearDistance:
-            if(!(props->MaxDistance != props->RefDistance))
-                ClampedDist = props->RefDistance;
-            else
-            {
-                ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
-                               (props->MaxDistance-props->RefDistance);
-                DryGain *= maxf(1.0f - attn, 0.0f);
-                for(i = 0;i < NumSends;i++)
-                {
-                    attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
-                           (props->MaxDistance-props->RefDistance);
-                    WetGain[i] *= maxf(1.0f - attn, 0.0f);
-                }
-            }
-            break;
-
-        case ExponentDistanceClamped:
-            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
-            if(props->MaxDistance < props->RefDistance)
-                break;
-            /*fall-through*/
-        case ExponentDistance:
-            if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
-                ClampedDist = props->RefDistance;
-            else
-            {
-                DryGain *= powf(ClampedDist/props->RefDistance, -props->RolloffFactor);
-                for(i = 0;i < NumSends;i++)
-                    WetGain[i] *= powf(ClampedDist/props->RefDistance, -RoomRolloff[i]);
-            }
-            break;
-
-        case DisableDistance:
-            ClampedDist = props->RefDistance;
-            break;
-    }
-
-    /* Calculate directional soundcones */
-    if(directional && props->InnerAngle < 360.0f)
-    {
-        ALfloat ConeVolume;
-        ALfloat ConeHF;
-        ALfloat Angle;
-
-        Angle = acosf(aluDotproduct(&Direction, &SourceToListener));
-        Angle = RAD2DEG(Angle * ConeScale * 2.0f);
-        if(!(Angle > props->InnerAngle))
-        {
-            ConeVolume = 1.0f;
-            ConeHF = 1.0f;
-        }
-        else if(Angle < props->OuterAngle)
-        {
-            ALfloat scale = (            Angle-props->InnerAngle) /
-                            (props->OuterAngle-props->InnerAngle);
-            ConeVolume = lerp(1.0f, props->OuterGain, scale);
-            ConeHF = lerp(1.0f, props->OuterGainHF, scale);
-        }
-        else
-        {
-            ConeVolume = props->OuterGain;
-            ConeHF = props->OuterGainHF;
-        }
-
-        DryGain *= ConeVolume;
-        if(props->DryGainHFAuto)
-            DryGainHF *= ConeHF;
-        if(props->WetGainAuto)
-        {
-            for(i = 0;i < NumSends;i++)
-                WetGain[i] *= ConeVolume;
-        }
-        if(props->WetGainHFAuto)
-        {
-            for(i = 0;i < NumSends;i++)
-                WetGainHF[i] *= ConeHF;
-        }
-    }
-
-    /* Apply gain and frequency filters */
-    DryGain  = clampf(DryGain, props->MinGain, props->MaxGain);
-    DryGain  = minf(DryGain*props->Direct.Gain*Listener->Params.Gain, GAIN_MIX_MAX);
-    DryGainHF *= props->Direct.GainHF;
-    DryGainLF *= props->Direct.GainLF;
-    for(i = 0;i < NumSends;i++)
-    {
-        WetGain[i]  = clampf(WetGain[i], props->MinGain, props->MaxGain);
-        WetGain[i]  = minf(WetGain[i]*props->Send[i].Gain*Listener->Params.Gain, GAIN_MIX_MAX);
-        WetGainHF[i] *= props->Send[i].GainHF;
-        WetGainLF[i] *= props->Send[i].GainLF;
-    }
-
-    /* Distance-based air absorption and initial send decay. */
-    if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
-    {
-        ALfloat meters_base = (ClampedDist-props->RefDistance) * props->RolloffFactor *
-                              Listener->Params.MetersPerUnit;
-        if(props->AirAbsorptionFactor > 0.0f)
-        {
-            ALfloat hfattn = powf(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor);
-            DryGainHF *= hfattn;
-            for(i = 0;i < NumSends;i++)
-                WetGainHF[i] *= hfattn;
-        }
-
-        if(props->WetGainAuto)
-        {
-            /* Apply a decay-time transformation to the wet path, based on the
-             * source distance in meters. The initial decay of the reverb
-             * effect is calculated and applied to the wet path.
-             */
-            for(i = 0;i < NumSends;i++)
-            {
-                ALfloat gain, gainhf, gainlf;
-
-                if(!(DecayDistance[i] > 0.0f))
-                    continue;
-
-                gain = powf(REVERB_DECAY_GAIN, meters_base/DecayDistance[i]);
-                WetGain[i] *= gain;
-                /* Yes, the wet path's air absorption is applied with
-                 * WetGainAuto on, rather than WetGainHFAuto.
-                 */
-                if(gain > 0.0f)
-                {
-                    gainhf = powf(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i]);
-                    WetGainHF[i] *= minf(gainhf / gain, 1.0f);
-                    gainlf = powf(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i]);
-                    WetGainLF[i] *= minf(gainlf / gain, 1.0f);
-                }
-            }
-        }
-    }
-
-
-    /* Initial source pitch */
-    Pitch = props->Pitch;
-
-    /* Calculate velocity-based doppler effect */
-    DopplerFactor = props->DopplerFactor * Listener->Params.DopplerFactor;
-    if(DopplerFactor > 0.0f)
-    {
-        const aluVector *lvelocity = &Listener->Params.Velocity;
-        const ALfloat SpeedOfSound = Listener->Params.SpeedOfSound;
-        ALfloat vss, vls;
-
-        vss = aluDotproduct(&Velocity, &SourceToListener) * DopplerFactor;
-        vls = aluDotproduct(lvelocity, &SourceToListener) * DopplerFactor;
-
-        if(!(vls < SpeedOfSound))
-        {
-            /* Listener moving away from the source at the speed of sound.
-             * Sound waves can't catch it.
-             */
-            Pitch = 0.0f;
-        }
-        else if(!(vss < SpeedOfSound))
-        {
-            /* Source moving toward the listener at the speed of sound. Sound
-             * waves bunch up to extreme frequencies.
-             */
-            Pitch = HUGE_VALF;
-        }
-        else
-        {
-            /* Source and listener movement is nominal. Calculate the proper
-             * doppler shift.
-             */
-            Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
-        }
-    }
-
-    /* Adjust pitch based on the buffer and output frequencies, and calculate
-     * fixed-point stepping value.
-     */
-    Pitch *= (ALfloat)ALBuffer->Frequency/(ALfloat)Device->Frequency;
-    if(Pitch > (ALfloat)MAX_PITCH)
-        voice->Step = MAX_PITCH<<FRACTIONBITS;
-    else
-        voice->Step = maxi(fastf2i(Pitch * FRACTIONONE), 1);
-    if(props->Resampler == BSinc24Resampler)
-        BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc24);
-    else if(props->Resampler == BSinc12Resampler)
-        BsincPrepare(voice->Step, &voice->ResampleState.bsinc, &bsinc12);
-    voice->Resampler = SelectResampler(props->Resampler);
-
-    if(Distance > 0.0f)
-    {
-        /* Clamp Y, in case rounding errors caused it to end up outside of
-         * -1...+1.
-         */
-        ev = asinf(clampf(-SourceToListener.v[1], -1.0f, 1.0f));
-        /* Double negation on Z cancels out; negate once for changing source-
-         * to-listener to listener-to-source, and again for right-handed coords
-         * with -Z in front.
-         */
-        az = atan2f(-SourceToListener.v[0], SourceToListener.v[2]*ZScale);
-    }
-    else
-        ev = az = 0.0f;
-
-    if(props->Radius > Distance)
-        spread = F_TAU - Distance/props->Radius*F_PI;
-    else if(Distance > 0.0f)
-        spread = asinf(props->Radius / Distance) * 2.0f;
-    else
-        spread = 0.0f;
-
-    CalcPanningAndFilters(voice, az, ev, Distance, spread, DryGain, DryGainHF, DryGainLF, WetGain,
-                          WetGainLF, WetGainHF, SendSlots, ALBuffer, props, Listener, Device);
-}
-
-static void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
-{
-    ALbufferlistitem *BufferListItem;
-    struct ALvoiceProps *props;
-
-    props = ATOMIC_EXCHANGE_PTR(&voice->Update, NULL, almemory_order_acq_rel);
-    if(!props && !force) return;
-
-    if(props)
-    {
-        memcpy(voice->Props, props,
-            FAM_SIZE(struct ALvoiceProps, Send, context->Device->NumAuxSends)
-        );
-
-        ATOMIC_REPLACE_HEAD(struct ALvoiceProps*, &context->FreeVoiceProps, props);
-    }
-    props = voice->Props;
-
-    BufferListItem = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
-    while(BufferListItem != NULL)
-    {
-        const ALbuffer *buffer = NULL;
-        ALsizei i = 0;
-        while(!buffer && i < BufferListItem->num_buffers)
-            buffer = BufferListItem->buffers[i];
-        if(LIKELY(buffer))
-        {
-            if(props->SpatializeMode == SpatializeOn ||
-               (props->SpatializeMode == SpatializeAuto && buffer->FmtChannels == FmtMono))
-                CalcAttnSourceParams(voice, props, buffer, context);
-            else
-                CalcNonAttnSourceParams(voice, props, buffer, context);
-            break;
-        }
-        BufferListItem = ATOMIC_LOAD(&BufferListItem->next, almemory_order_acquire);
-    }
-}
-
-
-static void ProcessParamUpdates(ALCcontext *ctx, const struct ALeffectslotArray *slots)
-{
-    ALvoice **voice, **voice_end;
-    ALsource *source;
-    ALsizei i;
-
-    IncrementRef(&ctx->UpdateCount);
-    if(!ATOMIC_LOAD(&ctx->HoldUpdates, almemory_order_acquire))
-    {
-        bool cforce = CalcContextParams(ctx);
-        bool force = CalcListenerParams(ctx) | cforce;
-        for(i = 0;i < slots->count;i++)
-            force |= CalcEffectSlotParams(slots->slot[i], ctx, cforce);
-
-        voice = ctx->Voices;
-        voice_end = voice + ctx->VoiceCount;
-        for(;voice != voice_end;++voice)
-        {
-            source = ATOMIC_LOAD(&(*voice)->Source, almemory_order_acquire);
-            if(source) CalcSourceParams(*voice, ctx, force);
-        }
-    }
-    IncrementRef(&ctx->UpdateCount);
-}
-
-
-static void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*restrict Buffer)[BUFFERSIZE],
-                           int lidx, int ridx, int cidx, ALsizei SamplesToDo,
-                           ALsizei NumChannels)
-{
-    ALfloat (*restrict lsplit)[BUFFERSIZE] = ASSUME_ALIGNED(Stablizer->LSplit, 16);
-    ALfloat (*restrict rsplit)[BUFFERSIZE] = ASSUME_ALIGNED(Stablizer->RSplit, 16);
-    ALsizei i;
-
-    /* Apply an all-pass to all channels, except the front-left and front-
-     * right, so they maintain the same relative phase.
-     */
-    for(i = 0;i < NumChannels;i++)
-    {
-        if(i == lidx || i == ridx)
-            continue;
-        splitterap_process(&Stablizer->APFilter[i], Buffer[i], SamplesToDo);
-    }
-
-    bandsplit_process(&Stablizer->LFilter, lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo);
-    bandsplit_process(&Stablizer->RFilter, rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo);
-
-    for(i = 0;i < SamplesToDo;i++)
-    {
-        ALfloat lfsum, hfsum;
-        ALfloat m, s, c;
-
-        lfsum = lsplit[0][i] + rsplit[0][i];
-        hfsum = lsplit[1][i] + rsplit[1][i];
-        s = lsplit[0][i] + lsplit[1][i] - rsplit[0][i] - rsplit[1][i];
-
-        /* This pans the separate low- and high-frequency sums between being on
-         * the center channel and the left/right channels. The low-frequency
-         * sum is 1/3rd toward center (2/3rds on left/right) and the high-
-         * frequency sum is 1/4th toward center (3/4ths on left/right). These
-         * values can be tweaked.
-         */
-        m = lfsum*cosf(1.0f/3.0f * F_PI_2) + hfsum*cosf(1.0f/4.0f * F_PI_2);
-        c = lfsum*sinf(1.0f/3.0f * F_PI_2) + hfsum*sinf(1.0f/4.0f * F_PI_2);
-
-        /* The generated center channel signal adds to the existing signal,
-         * while the modified left and right channels replace.
-         */
-        Buffer[lidx][i] = (m + s) * 0.5f;
-        Buffer[ridx][i] = (m - s) * 0.5f;
-        Buffer[cidx][i] += c * 0.5f;
-    }
-}
-
-static void ApplyDistanceComp(ALfloat (*restrict Samples)[BUFFERSIZE], DistanceComp *distcomp,
-                              ALfloat *restrict Values, ALsizei SamplesToDo, ALsizei numchans)
-{
-    ALsizei i, c;
-
-    Values = ASSUME_ALIGNED(Values, 16);
-    for(c = 0;c < numchans;c++)
-    {
-        ALfloat *restrict inout = ASSUME_ALIGNED(Samples[c], 16);
-        const ALfloat gain = distcomp[c].Gain;
-        const ALsizei base = distcomp[c].Length;
-        ALfloat *restrict distbuf = ASSUME_ALIGNED(distcomp[c].Buffer, 16);
-
-        if(base == 0)
-        {
-            if(gain < 1.0f)
-            {
-                for(i = 0;i < SamplesToDo;i++)
-                    inout[i] *= gain;
-            }
-            continue;
-        }
-
-        if(SamplesToDo >= base)
-        {
-            for(i = 0;i < base;i++)
-                Values[i] = distbuf[i];
-            for(;i < SamplesToDo;i++)
-                Values[i] = inout[i-base];
-            memcpy(distbuf, &inout[SamplesToDo-base], base*sizeof(ALfloat));
-        }
-        else
-        {
-            for(i = 0;i < SamplesToDo;i++)
-                Values[i] = distbuf[i];
-            memmove(distbuf, distbuf+SamplesToDo, (base-SamplesToDo)*sizeof(ALfloat));
-            memcpy(distbuf+base-SamplesToDo, inout, SamplesToDo*sizeof(ALfloat));
-        }
-        for(i = 0;i < SamplesToDo;i++)
-            inout[i] = Values[i]*gain;
-    }
-}
-
-static void ApplyDither(ALfloat (*restrict Samples)[BUFFERSIZE], ALuint *dither_seed,
-                        const ALfloat quant_scale, const ALsizei SamplesToDo,
-                        const ALsizei numchans)
-{
-    const ALfloat invscale = 1.0f / quant_scale;
-    ALuint seed = *dither_seed;
-    ALsizei c, i;
-
-    /* Dithering. Step 1, generate whitenoise (uniform distribution of random
-     * values between -1 and +1). Step 2 is to add the noise to the samples,
-     * before rounding and after scaling up to the desired quantization depth.
-     */
-    for(c = 0;c < numchans;c++)
-    {
-        ALfloat *restrict samples = Samples[c];
-        for(i = 0;i < SamplesToDo;i++)
-        {
-            ALfloat val = samples[i] * quant_scale;
-            ALuint rng0 = dither_rng(&seed);
-            ALuint rng1 = dither_rng(&seed);
-            val += (ALfloat)(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
-            samples[i] = fastf2i(val) * invscale;
-        }
-    }
-    *dither_seed = seed;
-}
-
-
-static inline ALfloat Conv_ALfloat(ALfloat val)
-{ return val; }
-static inline ALint Conv_ALint(ALfloat val)
-{
-    /* Floats have a 23-bit mantissa. A bit of the exponent helps out along
-     * with the sign bit, giving 25 bits. So [-16777216, +16777216] is the max
-     * integer range normalized floats can be converted to before losing
-     * precision.
-     */
-    return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f))<<7;
-}
-static inline ALshort Conv_ALshort(ALfloat val)
-{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
-static inline ALbyte Conv_ALbyte(ALfloat val)
-{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
-
-/* Define unsigned output variations. */
-#define DECL_TEMPLATE(T, func, O)                             \
-static inline T Conv_##T(ALfloat val) { return func(val)+O; }
-
-DECL_TEMPLATE(ALubyte, Conv_ALbyte, 128)
-DECL_TEMPLATE(ALushort, Conv_ALshort, 32768)
-DECL_TEMPLATE(ALuint, Conv_ALint, 2147483648u)
-
-#undef DECL_TEMPLATE
-
-#define DECL_TEMPLATE(T, A)                                                   \
-static void Write##A(const ALfloat (*restrict InBuffer)[BUFFERSIZE],          \
-                     ALvoid *OutBuffer, ALsizei Offset, ALsizei SamplesToDo,  \
-                     ALsizei numchans)                                        \
-{                                                                             \
-    ALsizei i, j;                                                             \
-    for(j = 0;j < numchans;j++)                                               \
-    {                                                                         \
-        const ALfloat *restrict in = ASSUME_ALIGNED(InBuffer[j], 16);         \
-        T *restrict out = (T*)OutBuffer + Offset*numchans + j;                \
-                                                                              \
-        for(i = 0;i < SamplesToDo;i++)                                        \
-            out[i*numchans] = Conv_##T(in[i]);                                \
-    }                                                                         \
-}
-
-DECL_TEMPLATE(ALfloat, F32)
-DECL_TEMPLATE(ALuint, UI32)
-DECL_TEMPLATE(ALint, I32)
-DECL_TEMPLATE(ALushort, UI16)
-DECL_TEMPLATE(ALshort, I16)
-DECL_TEMPLATE(ALubyte, UI8)
-DECL_TEMPLATE(ALbyte, I8)
-
-#undef DECL_TEMPLATE
-
-
-void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples)
-{
-    ALsizei SamplesToDo;
-    ALsizei SamplesDone;
-    ALCcontext *ctx;
-    ALsizei i, c;
-
-    START_MIXER_MODE();
-    for(SamplesDone = 0;SamplesDone < NumSamples;)
-    {
-        SamplesToDo = mini(NumSamples-SamplesDone, BUFFERSIZE);
-        for(c = 0;c < device->Dry.NumChannels;c++)
-            memset(device->Dry.Buffer[c], 0, SamplesToDo*sizeof(ALfloat));
-        if(device->Dry.Buffer != device->FOAOut.Buffer)
-            for(c = 0;c < device->FOAOut.NumChannels;c++)
-                memset(device->FOAOut.Buffer[c], 0, SamplesToDo*sizeof(ALfloat));
-        if(device->Dry.Buffer != device->RealOut.Buffer)
-            for(c = 0;c < device->RealOut.NumChannels;c++)
-                memset(device->RealOut.Buffer[c], 0, SamplesToDo*sizeof(ALfloat));
-
-        IncrementRef(&device->MixCount);
-
-        ctx = ATOMIC_LOAD(&device->ContextList, almemory_order_acquire);
-        while(ctx)
-        {
-            const struct ALeffectslotArray *auxslots;
-
-            auxslots = ATOMIC_LOAD(&ctx->ActiveAuxSlots, almemory_order_acquire);
-            ProcessParamUpdates(ctx, auxslots);
-
-            for(i = 0;i < auxslots->count;i++)
-            {
-                ALeffectslot *slot = auxslots->slot[i];
-                for(c = 0;c < slot->NumChannels;c++)
-                    memset(slot->WetBuffer[c], 0, SamplesToDo*sizeof(ALfloat));
-            }
-
-            /* source processing */
-            for(i = 0;i < ctx->VoiceCount;i++)
-            {
-                ALvoice *voice = ctx->Voices[i];
-                ALsource *source = ATOMIC_LOAD(&voice->Source, almemory_order_acquire);
-                if(source && ATOMIC_LOAD(&voice->Playing, almemory_order_relaxed) &&
-                   voice->Step > 0)
-                {
-                    if(!MixSource(voice, source->id, ctx, SamplesToDo))
-                    {
-                        ATOMIC_STORE(&voice->Source, NULL, almemory_order_relaxed);
-                        ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
-                        SendSourceStoppedEvent(ctx, source->id);
-                    }
-                }
-            }
-
-            /* effect slot processing */
-            for(i = 0;i < auxslots->count;i++)
-            {
-                const ALeffectslot *slot = auxslots->slot[i];
-                ALeffectState *state = slot->Params.EffectState;
-                V(state,process)(SamplesToDo, slot->WetBuffer, state->OutBuffer,
-                                 state->OutChannels);
-            }
-
-            ctx = ATOMIC_LOAD(&ctx->next, almemory_order_relaxed);
-        }
-
-        /* Increment the clock time. Every second's worth of samples is
-         * converted and added to clock base so that large sample counts don't
-         * overflow during conversion. This also guarantees an exact, stable
-         * conversion. */
-        device->SamplesDone += SamplesToDo;
-        device->ClockBase += (device->SamplesDone/device->Frequency) * DEVICE_CLOCK_RES;
-        device->SamplesDone %= device->Frequency;
-        IncrementRef(&device->MixCount);
-
-        /* Apply post-process for finalizing the Dry mix to the RealOut
-         * (Ambisonic decode, UHJ encode, etc).
-         */
-        if(LIKELY(device->PostProcess))
-            device->PostProcess(device, SamplesToDo);
-
-        if(device->Stablizer)
-        {
-            int lidx = GetChannelIdxByName(&device->RealOut, FrontLeft);
-            int ridx = GetChannelIdxByName(&device->RealOut, FrontRight);
-            int cidx = GetChannelIdxByName(&device->RealOut, FrontCenter);
-            assert(lidx >= 0 && ridx >= 0 && cidx >= 0);
-
-            ApplyStablizer(device->Stablizer, device->RealOut.Buffer, lidx, ridx, cidx,
-                           SamplesToDo, device->RealOut.NumChannels);
-        }
-
-        ApplyDistanceComp(device->RealOut.Buffer, device->ChannelDelay, device->TempBuffer[0],
-                          SamplesToDo, device->RealOut.NumChannels);
-
-        if(device->Limiter)
-            ApplyCompression(device->Limiter, device->RealOut.NumChannels, SamplesToDo,
-                             device->RealOut.Buffer);
-
-        if(device->DitherDepth > 0.0f)
-            ApplyDither(device->RealOut.Buffer, &device->DitherSeed, device->DitherDepth,
-                        SamplesToDo, device->RealOut.NumChannels);
-
-        if(LIKELY(OutBuffer))
-        {
-            ALfloat (*Buffer)[BUFFERSIZE] = device->RealOut.Buffer;
-            ALsizei Channels = device->RealOut.NumChannels;
-
-            switch(device->FmtType)
-            {
-                case DevFmtByte:
-                    WriteI8(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtUByte:
-                    WriteUI8(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtShort:
-                    WriteI16(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtUShort:
-                    WriteUI16(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtInt:
-                    WriteI32(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtUInt:
-                    WriteUI32(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-                case DevFmtFloat:
-                    WriteF32(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels);
-                    break;
-            }
-        }
-
-        SamplesDone += SamplesToDo;
-    }
-    END_MIXER_MODE();
-}
-
-
-void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
-{
-    ALCcontext *ctx;
-    AsyncEvent evt;
-    va_list args;
-    int msglen;
-
-    if(!ATOMIC_EXCHANGE(&device->Connected, AL_FALSE, almemory_order_acq_rel))
-        return;
-
-    evt.EnumType = EventType_Disconnected;
-    evt.Type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
-    evt.ObjectId = 0;
-    evt.Param = 0;
-
-    va_start(args, msg);
-    msglen = vsnprintf(evt.Message, sizeof(evt.Message), msg, args);
-    va_end(args);
-
-    if(msglen < 0 || (size_t)msglen >= sizeof(evt.Message))
-    {
-        evt.Message[sizeof(evt.Message)-1] = 0;
-        msglen = (int)strlen(evt.Message);
-    }
-    if(msglen > 0)
-        msg = evt.Message;
-    else
-    {
-        msg = "<internal error constructing message>";
-        msglen = (int)strlen(msg);
-    }
-
-    ctx = ATOMIC_LOAD_SEQ(&device->ContextList);
-    while(ctx)
-    {
-        ALbitfieldSOFT enabledevt = ATOMIC_LOAD(&ctx->EnabledEvts, almemory_order_acquire);
-        ALsizei i;
-
-        if((enabledevt&EventType_Disconnected) &&
-           ll_ringbuffer_write(ctx->AsyncEvents, (const char*)&evt, 1) == 1)
-            alsem_post(&ctx->EventSem);
-
-        for(i = 0;i < ctx->VoiceCount;i++)
-        {
-            ALvoice *voice = ctx->Voices[i];
-            ALsource *source;
-
-            source = ATOMIC_EXCHANGE_PTR(&voice->Source, NULL, almemory_order_relaxed);
-            if(source && ATOMIC_LOAD(&voice->Playing, almemory_order_relaxed))
-            {
-                /* If the source's voice was playing, it's now effectively
-                 * stopped (the source state will be updated the next time it's
-                 * checked).
-                 */
-                SendSourceStoppedEvent(ctx, source->id);
-            }
-            ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
-        }
-
-        ctx = ATOMIC_LOAD(&ctx->next, almemory_order_relaxed);
-    }
-}

+ 4136 - 0
Engine/lib/openal-soft/Alc/alc.cpp

@@ -0,0 +1,4136 @@
+/**
+ * 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 "version.h"
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include <exception>
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cctype>
+#include <chrono>
+#include <cinttypes>
+#include <climits>
+#include <cmath>
+#include <csignal>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+#include "AL/efx.h"
+
+#include "al/auxeffectslot.h"
+#include "al/buffer.h"
+#include "al/effect.h"
+#include "al/event.h"
+#include "al/filter.h"
+#include "al/listener.h"
+#include "al/source.h"
+#include "albit.h"
+#include "alcmain.h"
+#include "albyte.h"
+#include "alconfig.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "alu.h"
+#include "async_event.h"
+#include "atomic.h"
+#include "bformatdec.h"
+#include "compat.h"
+#include "core/ambidefs.h"
+#include "core/bs2b.h"
+#include "core/cpu_caps.h"
+#include "core/devformat.h"
+#include "core/except.h"
+#include "core/mastering.h"
+#include "core/filters/nfc.h"
+#include "core/filters/splitter.h"
+#include "core/fpu_ctrl.h"
+#include "core/logging.h"
+#include "core/uhjfilter.h"
+#include "effects/base.h"
+#include "front_stablizer.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "intrusive_ptr.h"
+#include "opthelpers.h"
+#include "pragmadefs.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+#include "vecmat.h"
+#include "vector.h"
+#include "voice_change.h"
+
+#include "backends/base.h"
+#include "backends/null.h"
+#include "backends/loopback.h"
+#ifdef HAVE_JACK
+#include "backends/jack.h"
+#endif
+#ifdef HAVE_PULSEAUDIO
+#include "backends/pulseaudio.h"
+#endif
+#ifdef HAVE_ALSA
+#include "backends/alsa.h"
+#endif
+#ifdef HAVE_WASAPI
+#include "backends/wasapi.h"
+#endif
+#ifdef HAVE_COREAUDIO
+#include "backends/coreaudio.h"
+#endif
+#ifdef HAVE_OPENSL
+#include "backends/opensl.h"
+#endif
+#ifdef HAVE_OBOE
+#include "backends/oboe.h"
+#endif
+#ifdef HAVE_SOLARIS
+#include "backends/solaris.h"
+#endif
+#ifdef HAVE_SNDIO
+#include "backends/sndio.h"
+#endif
+#ifdef HAVE_OSS
+#include "backends/oss.h"
+#endif
+#ifdef HAVE_DSOUND
+#include "backends/dsound.h"
+#endif
+#ifdef HAVE_WINMM
+#include "backends/winmm.h"
+#endif
+#ifdef HAVE_PORTAUDIO
+#include "backends/portaudio.h"
+#endif
+#ifdef HAVE_SDL2
+#include "backends/sdl2.h"
+#endif
+#ifdef HAVE_WAVE
+#include "backends/wave.h"
+#endif
+
+
+namespace {
+
+using namespace std::placeholders;
+using std::chrono::seconds;
+using std::chrono::nanoseconds;
+
+using voidp = void*;
+
+
+/************************************************
+ * Backends
+ ************************************************/
+struct BackendInfo {
+    const char *name;
+    BackendFactory& (*getFactory)(void);
+};
+
+BackendInfo BackendList[] = {
+#ifdef HAVE_JACK
+    { "jack", JackBackendFactory::getFactory },
+#endif
+#ifdef HAVE_PULSEAUDIO
+    { "pulse", PulseBackendFactory::getFactory },
+#endif
+#ifdef HAVE_ALSA
+    { "alsa", AlsaBackendFactory::getFactory },
+#endif
+#ifdef HAVE_WASAPI
+    { "wasapi", WasapiBackendFactory::getFactory },
+#endif
+#ifdef HAVE_COREAUDIO
+    { "core", CoreAudioBackendFactory::getFactory },
+#endif
+#ifdef HAVE_OBOE
+    { "oboe", OboeBackendFactory::getFactory },
+#endif
+#ifdef HAVE_OPENSL
+    { "opensl", OSLBackendFactory::getFactory },
+#endif
+#ifdef HAVE_SOLARIS
+    { "solaris", SolarisBackendFactory::getFactory },
+#endif
+#ifdef HAVE_SNDIO
+    { "sndio", SndIOBackendFactory::getFactory },
+#endif
+#ifdef HAVE_OSS
+    { "oss", OSSBackendFactory::getFactory },
+#endif
+#ifdef HAVE_DSOUND
+    { "dsound", DSoundBackendFactory::getFactory },
+#endif
+#ifdef HAVE_WINMM
+    { "winmm", WinMMBackendFactory::getFactory },
+#endif
+#ifdef HAVE_PORTAUDIO
+    { "port", PortBackendFactory::getFactory },
+#endif
+#ifdef HAVE_SDL2
+    { "sdl2", SDL2BackendFactory::getFactory },
+#endif
+
+    { "null", NullBackendFactory::getFactory },
+#ifdef HAVE_WAVE
+    { "wave", WaveBackendFactory::getFactory },
+#endif
+};
+
+BackendFactory *PlaybackFactory{};
+BackendFactory *CaptureFactory{};
+
+
+/************************************************
+ * Functions, enums, and errors
+ ************************************************/
+#define DECL(x) { #x, reinterpret_cast<void*>(x) }
+const struct {
+    const char *funcName;
+    void *address;
+} alcFunctions[] = {
+    DECL(alcCreateContext),
+    DECL(alcMakeContextCurrent),
+    DECL(alcProcessContext),
+    DECL(alcSuspendContext),
+    DECL(alcDestroyContext),
+    DECL(alcGetCurrentContext),
+    DECL(alcGetContextsDevice),
+    DECL(alcOpenDevice),
+    DECL(alcCloseDevice),
+    DECL(alcGetError),
+    DECL(alcIsExtensionPresent),
+    DECL(alcGetProcAddress),
+    DECL(alcGetEnumValue),
+    DECL(alcGetString),
+    DECL(alcGetIntegerv),
+    DECL(alcCaptureOpenDevice),
+    DECL(alcCaptureCloseDevice),
+    DECL(alcCaptureStart),
+    DECL(alcCaptureStop),
+    DECL(alcCaptureSamples),
+
+    DECL(alcSetThreadContext),
+    DECL(alcGetThreadContext),
+
+    DECL(alcLoopbackOpenDeviceSOFT),
+    DECL(alcIsRenderFormatSupportedSOFT),
+    DECL(alcRenderSamplesSOFT),
+
+    DECL(alcDevicePauseSOFT),
+    DECL(alcDeviceResumeSOFT),
+
+    DECL(alcGetStringiSOFT),
+    DECL(alcResetDeviceSOFT),
+
+    DECL(alcGetInteger64vSOFT),
+
+    DECL(alEnable),
+    DECL(alDisable),
+    DECL(alIsEnabled),
+    DECL(alGetString),
+    DECL(alGetBooleanv),
+    DECL(alGetIntegerv),
+    DECL(alGetFloatv),
+    DECL(alGetDoublev),
+    DECL(alGetBoolean),
+    DECL(alGetInteger),
+    DECL(alGetFloat),
+    DECL(alGetDouble),
+    DECL(alGetError),
+    DECL(alIsExtensionPresent),
+    DECL(alGetProcAddress),
+    DECL(alGetEnumValue),
+    DECL(alListenerf),
+    DECL(alListener3f),
+    DECL(alListenerfv),
+    DECL(alListeneri),
+    DECL(alListener3i),
+    DECL(alListeneriv),
+    DECL(alGetListenerf),
+    DECL(alGetListener3f),
+    DECL(alGetListenerfv),
+    DECL(alGetListeneri),
+    DECL(alGetListener3i),
+    DECL(alGetListeneriv),
+    DECL(alGenSources),
+    DECL(alDeleteSources),
+    DECL(alIsSource),
+    DECL(alSourcef),
+    DECL(alSource3f),
+    DECL(alSourcefv),
+    DECL(alSourcei),
+    DECL(alSource3i),
+    DECL(alSourceiv),
+    DECL(alGetSourcef),
+    DECL(alGetSource3f),
+    DECL(alGetSourcefv),
+    DECL(alGetSourcei),
+    DECL(alGetSource3i),
+    DECL(alGetSourceiv),
+    DECL(alSourcePlayv),
+    DECL(alSourceStopv),
+    DECL(alSourceRewindv),
+    DECL(alSourcePausev),
+    DECL(alSourcePlay),
+    DECL(alSourceStop),
+    DECL(alSourceRewind),
+    DECL(alSourcePause),
+    DECL(alSourceQueueBuffers),
+    DECL(alSourceUnqueueBuffers),
+    DECL(alGenBuffers),
+    DECL(alDeleteBuffers),
+    DECL(alIsBuffer),
+    DECL(alBufferData),
+    DECL(alBufferf),
+    DECL(alBuffer3f),
+    DECL(alBufferfv),
+    DECL(alBufferi),
+    DECL(alBuffer3i),
+    DECL(alBufferiv),
+    DECL(alGetBufferf),
+    DECL(alGetBuffer3f),
+    DECL(alGetBufferfv),
+    DECL(alGetBufferi),
+    DECL(alGetBuffer3i),
+    DECL(alGetBufferiv),
+    DECL(alDopplerFactor),
+    DECL(alDopplerVelocity),
+    DECL(alSpeedOfSound),
+    DECL(alDistanceModel),
+
+    DECL(alGenFilters),
+    DECL(alDeleteFilters),
+    DECL(alIsFilter),
+    DECL(alFilteri),
+    DECL(alFilteriv),
+    DECL(alFilterf),
+    DECL(alFilterfv),
+    DECL(alGetFilteri),
+    DECL(alGetFilteriv),
+    DECL(alGetFilterf),
+    DECL(alGetFilterfv),
+    DECL(alGenEffects),
+    DECL(alDeleteEffects),
+    DECL(alIsEffect),
+    DECL(alEffecti),
+    DECL(alEffectiv),
+    DECL(alEffectf),
+    DECL(alEffectfv),
+    DECL(alGetEffecti),
+    DECL(alGetEffectiv),
+    DECL(alGetEffectf),
+    DECL(alGetEffectfv),
+    DECL(alGenAuxiliaryEffectSlots),
+    DECL(alDeleteAuxiliaryEffectSlots),
+    DECL(alIsAuxiliaryEffectSlot),
+    DECL(alAuxiliaryEffectSloti),
+    DECL(alAuxiliaryEffectSlotiv),
+    DECL(alAuxiliaryEffectSlotf),
+    DECL(alAuxiliaryEffectSlotfv),
+    DECL(alGetAuxiliaryEffectSloti),
+    DECL(alGetAuxiliaryEffectSlotiv),
+    DECL(alGetAuxiliaryEffectSlotf),
+    DECL(alGetAuxiliaryEffectSlotfv),
+
+    DECL(alDeferUpdatesSOFT),
+    DECL(alProcessUpdatesSOFT),
+
+    DECL(alSourcedSOFT),
+    DECL(alSource3dSOFT),
+    DECL(alSourcedvSOFT),
+    DECL(alGetSourcedSOFT),
+    DECL(alGetSource3dSOFT),
+    DECL(alGetSourcedvSOFT),
+    DECL(alSourcei64SOFT),
+    DECL(alSource3i64SOFT),
+    DECL(alSourcei64vSOFT),
+    DECL(alGetSourcei64SOFT),
+    DECL(alGetSource3i64SOFT),
+    DECL(alGetSourcei64vSOFT),
+
+    DECL(alGetStringiSOFT),
+
+    DECL(alBufferStorageSOFT),
+    DECL(alMapBufferSOFT),
+    DECL(alUnmapBufferSOFT),
+    DECL(alFlushMappedBufferSOFT),
+
+    DECL(alEventControlSOFT),
+    DECL(alEventCallbackSOFT),
+    DECL(alGetPointerSOFT),
+    DECL(alGetPointervSOFT),
+
+    DECL(alBufferCallbackSOFT),
+    DECL(alGetBufferPtrSOFT),
+    DECL(alGetBuffer3PtrSOFT),
+    DECL(alGetBufferPtrvSOFT),
+
+    DECL(alAuxiliaryEffectSlotPlaySOFT),
+    DECL(alAuxiliaryEffectSlotPlayvSOFT),
+    DECL(alAuxiliaryEffectSlotStopSOFT),
+    DECL(alAuxiliaryEffectSlotStopvSOFT),
+};
+#undef DECL
+
+#define DECL(x) { #x, (x) }
+constexpr struct {
+    const ALCchar *enumName;
+    ALCenum value;
+} alcEnumerations[] = {
+    DECL(ALC_INVALID),
+    DECL(ALC_FALSE),
+    DECL(ALC_TRUE),
+
+    DECL(ALC_MAJOR_VERSION),
+    DECL(ALC_MINOR_VERSION),
+    DECL(ALC_ATTRIBUTES_SIZE),
+    DECL(ALC_ALL_ATTRIBUTES),
+    DECL(ALC_DEFAULT_DEVICE_SPECIFIER),
+    DECL(ALC_DEVICE_SPECIFIER),
+    DECL(ALC_ALL_DEVICES_SPECIFIER),
+    DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER),
+    DECL(ALC_EXTENSIONS),
+    DECL(ALC_FREQUENCY),
+    DECL(ALC_REFRESH),
+    DECL(ALC_SYNC),
+    DECL(ALC_MONO_SOURCES),
+    DECL(ALC_STEREO_SOURCES),
+    DECL(ALC_CAPTURE_DEVICE_SPECIFIER),
+    DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER),
+    DECL(ALC_CAPTURE_SAMPLES),
+    DECL(ALC_CONNECTED),
+
+    DECL(ALC_EFX_MAJOR_VERSION),
+    DECL(ALC_EFX_MINOR_VERSION),
+    DECL(ALC_MAX_AUXILIARY_SENDS),
+
+    DECL(ALC_FORMAT_CHANNELS_SOFT),
+    DECL(ALC_FORMAT_TYPE_SOFT),
+
+    DECL(ALC_MONO_SOFT),
+    DECL(ALC_STEREO_SOFT),
+    DECL(ALC_QUAD_SOFT),
+    DECL(ALC_5POINT1_SOFT),
+    DECL(ALC_6POINT1_SOFT),
+    DECL(ALC_7POINT1_SOFT),
+    DECL(ALC_BFORMAT3D_SOFT),
+
+    DECL(ALC_BYTE_SOFT),
+    DECL(ALC_UNSIGNED_BYTE_SOFT),
+    DECL(ALC_SHORT_SOFT),
+    DECL(ALC_UNSIGNED_SHORT_SOFT),
+    DECL(ALC_INT_SOFT),
+    DECL(ALC_UNSIGNED_INT_SOFT),
+    DECL(ALC_FLOAT_SOFT),
+
+    DECL(ALC_HRTF_SOFT),
+    DECL(ALC_DONT_CARE_SOFT),
+    DECL(ALC_HRTF_STATUS_SOFT),
+    DECL(ALC_HRTF_DISABLED_SOFT),
+    DECL(ALC_HRTF_ENABLED_SOFT),
+    DECL(ALC_HRTF_DENIED_SOFT),
+    DECL(ALC_HRTF_REQUIRED_SOFT),
+    DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT),
+    DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT),
+    DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT),
+    DECL(ALC_HRTF_SPECIFIER_SOFT),
+    DECL(ALC_HRTF_ID_SOFT),
+
+    DECL(ALC_AMBISONIC_LAYOUT_SOFT),
+    DECL(ALC_AMBISONIC_SCALING_SOFT),
+    DECL(ALC_AMBISONIC_ORDER_SOFT),
+    DECL(ALC_ACN_SOFT),
+    DECL(ALC_FUMA_SOFT),
+    DECL(ALC_N3D_SOFT),
+    DECL(ALC_SN3D_SOFT),
+
+    DECL(ALC_OUTPUT_LIMITER_SOFT),
+
+    DECL(ALC_NO_ERROR),
+    DECL(ALC_INVALID_DEVICE),
+    DECL(ALC_INVALID_CONTEXT),
+    DECL(ALC_INVALID_ENUM),
+    DECL(ALC_INVALID_VALUE),
+    DECL(ALC_OUT_OF_MEMORY),
+
+
+    DECL(AL_INVALID),
+    DECL(AL_NONE),
+    DECL(AL_FALSE),
+    DECL(AL_TRUE),
+
+    DECL(AL_SOURCE_RELATIVE),
+    DECL(AL_CONE_INNER_ANGLE),
+    DECL(AL_CONE_OUTER_ANGLE),
+    DECL(AL_PITCH),
+    DECL(AL_POSITION),
+    DECL(AL_DIRECTION),
+    DECL(AL_VELOCITY),
+    DECL(AL_LOOPING),
+    DECL(AL_BUFFER),
+    DECL(AL_GAIN),
+    DECL(AL_MIN_GAIN),
+    DECL(AL_MAX_GAIN),
+    DECL(AL_ORIENTATION),
+    DECL(AL_REFERENCE_DISTANCE),
+    DECL(AL_ROLLOFF_FACTOR),
+    DECL(AL_CONE_OUTER_GAIN),
+    DECL(AL_MAX_DISTANCE),
+    DECL(AL_SEC_OFFSET),
+    DECL(AL_SAMPLE_OFFSET),
+    DECL(AL_BYTE_OFFSET),
+    DECL(AL_SOURCE_TYPE),
+    DECL(AL_STATIC),
+    DECL(AL_STREAMING),
+    DECL(AL_UNDETERMINED),
+    DECL(AL_METERS_PER_UNIT),
+    DECL(AL_LOOP_POINTS_SOFT),
+    DECL(AL_DIRECT_CHANNELS_SOFT),
+
+    DECL(AL_DIRECT_FILTER),
+    DECL(AL_AUXILIARY_SEND_FILTER),
+    DECL(AL_AIR_ABSORPTION_FACTOR),
+    DECL(AL_ROOM_ROLLOFF_FACTOR),
+    DECL(AL_CONE_OUTER_GAINHF),
+    DECL(AL_DIRECT_FILTER_GAINHF_AUTO),
+    DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO),
+    DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO),
+
+    DECL(AL_SOURCE_STATE),
+    DECL(AL_INITIAL),
+    DECL(AL_PLAYING),
+    DECL(AL_PAUSED),
+    DECL(AL_STOPPED),
+
+    DECL(AL_BUFFERS_QUEUED),
+    DECL(AL_BUFFERS_PROCESSED),
+
+    DECL(AL_FORMAT_MONO8),
+    DECL(AL_FORMAT_MONO16),
+    DECL(AL_FORMAT_MONO_FLOAT32),
+    DECL(AL_FORMAT_MONO_DOUBLE_EXT),
+    DECL(AL_FORMAT_STEREO8),
+    DECL(AL_FORMAT_STEREO16),
+    DECL(AL_FORMAT_STEREO_FLOAT32),
+    DECL(AL_FORMAT_STEREO_DOUBLE_EXT),
+    DECL(AL_FORMAT_MONO_IMA4),
+    DECL(AL_FORMAT_STEREO_IMA4),
+    DECL(AL_FORMAT_MONO_MSADPCM_SOFT),
+    DECL(AL_FORMAT_STEREO_MSADPCM_SOFT),
+    DECL(AL_FORMAT_QUAD8_LOKI),
+    DECL(AL_FORMAT_QUAD16_LOKI),
+    DECL(AL_FORMAT_QUAD8),
+    DECL(AL_FORMAT_QUAD16),
+    DECL(AL_FORMAT_QUAD32),
+    DECL(AL_FORMAT_51CHN8),
+    DECL(AL_FORMAT_51CHN16),
+    DECL(AL_FORMAT_51CHN32),
+    DECL(AL_FORMAT_61CHN8),
+    DECL(AL_FORMAT_61CHN16),
+    DECL(AL_FORMAT_61CHN32),
+    DECL(AL_FORMAT_71CHN8),
+    DECL(AL_FORMAT_71CHN16),
+    DECL(AL_FORMAT_71CHN32),
+    DECL(AL_FORMAT_REAR8),
+    DECL(AL_FORMAT_REAR16),
+    DECL(AL_FORMAT_REAR32),
+    DECL(AL_FORMAT_MONO_MULAW),
+    DECL(AL_FORMAT_MONO_MULAW_EXT),
+    DECL(AL_FORMAT_STEREO_MULAW),
+    DECL(AL_FORMAT_STEREO_MULAW_EXT),
+    DECL(AL_FORMAT_QUAD_MULAW),
+    DECL(AL_FORMAT_51CHN_MULAW),
+    DECL(AL_FORMAT_61CHN_MULAW),
+    DECL(AL_FORMAT_71CHN_MULAW),
+    DECL(AL_FORMAT_REAR_MULAW),
+    DECL(AL_FORMAT_MONO_ALAW_EXT),
+    DECL(AL_FORMAT_STEREO_ALAW_EXT),
+
+    DECL(AL_FORMAT_BFORMAT2D_8),
+    DECL(AL_FORMAT_BFORMAT2D_16),
+    DECL(AL_FORMAT_BFORMAT2D_FLOAT32),
+    DECL(AL_FORMAT_BFORMAT2D_MULAW),
+    DECL(AL_FORMAT_BFORMAT3D_8),
+    DECL(AL_FORMAT_BFORMAT3D_16),
+    DECL(AL_FORMAT_BFORMAT3D_FLOAT32),
+    DECL(AL_FORMAT_BFORMAT3D_MULAW),
+
+    DECL(AL_FREQUENCY),
+    DECL(AL_BITS),
+    DECL(AL_CHANNELS),
+    DECL(AL_SIZE),
+    DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT),
+    DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT),
+
+    DECL(AL_SOURCE_RADIUS),
+
+    DECL(AL_STEREO_ANGLES),
+
+    DECL(AL_UNUSED),
+    DECL(AL_PENDING),
+    DECL(AL_PROCESSED),
+
+    DECL(AL_NO_ERROR),
+    DECL(AL_INVALID_NAME),
+    DECL(AL_INVALID_ENUM),
+    DECL(AL_INVALID_VALUE),
+    DECL(AL_INVALID_OPERATION),
+    DECL(AL_OUT_OF_MEMORY),
+
+    DECL(AL_VENDOR),
+    DECL(AL_VERSION),
+    DECL(AL_RENDERER),
+    DECL(AL_EXTENSIONS),
+
+    DECL(AL_DOPPLER_FACTOR),
+    DECL(AL_DOPPLER_VELOCITY),
+    DECL(AL_DISTANCE_MODEL),
+    DECL(AL_SPEED_OF_SOUND),
+    DECL(AL_SOURCE_DISTANCE_MODEL),
+    DECL(AL_DEFERRED_UPDATES_SOFT),
+    DECL(AL_GAIN_LIMIT_SOFT),
+
+    DECL(AL_INVERSE_DISTANCE),
+    DECL(AL_INVERSE_DISTANCE_CLAMPED),
+    DECL(AL_LINEAR_DISTANCE),
+    DECL(AL_LINEAR_DISTANCE_CLAMPED),
+    DECL(AL_EXPONENT_DISTANCE),
+    DECL(AL_EXPONENT_DISTANCE_CLAMPED),
+
+    DECL(AL_FILTER_TYPE),
+    DECL(AL_FILTER_NULL),
+    DECL(AL_FILTER_LOWPASS),
+    DECL(AL_FILTER_HIGHPASS),
+    DECL(AL_FILTER_BANDPASS),
+
+    DECL(AL_LOWPASS_GAIN),
+    DECL(AL_LOWPASS_GAINHF),
+
+    DECL(AL_HIGHPASS_GAIN),
+    DECL(AL_HIGHPASS_GAINLF),
+
+    DECL(AL_BANDPASS_GAIN),
+    DECL(AL_BANDPASS_GAINHF),
+    DECL(AL_BANDPASS_GAINLF),
+
+    DECL(AL_EFFECT_TYPE),
+    DECL(AL_EFFECT_NULL),
+    DECL(AL_EFFECT_REVERB),
+    DECL(AL_EFFECT_EAXREVERB),
+    DECL(AL_EFFECT_CHORUS),
+    DECL(AL_EFFECT_DISTORTION),
+    DECL(AL_EFFECT_ECHO),
+    DECL(AL_EFFECT_FLANGER),
+    DECL(AL_EFFECT_PITCH_SHIFTER),
+    DECL(AL_EFFECT_FREQUENCY_SHIFTER),
+    DECL(AL_EFFECT_VOCAL_MORPHER),
+    DECL(AL_EFFECT_RING_MODULATOR),
+    DECL(AL_EFFECT_AUTOWAH),
+    DECL(AL_EFFECT_COMPRESSOR),
+    DECL(AL_EFFECT_EQUALIZER),
+    DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT),
+    DECL(AL_EFFECT_DEDICATED_DIALOGUE),
+
+    DECL(AL_EFFECTSLOT_EFFECT),
+    DECL(AL_EFFECTSLOT_GAIN),
+    DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO),
+    DECL(AL_EFFECTSLOT_NULL),
+
+    DECL(AL_EAXREVERB_DENSITY),
+    DECL(AL_EAXREVERB_DIFFUSION),
+    DECL(AL_EAXREVERB_GAIN),
+    DECL(AL_EAXREVERB_GAINHF),
+    DECL(AL_EAXREVERB_GAINLF),
+    DECL(AL_EAXREVERB_DECAY_TIME),
+    DECL(AL_EAXREVERB_DECAY_HFRATIO),
+    DECL(AL_EAXREVERB_DECAY_LFRATIO),
+    DECL(AL_EAXREVERB_REFLECTIONS_GAIN),
+    DECL(AL_EAXREVERB_REFLECTIONS_DELAY),
+    DECL(AL_EAXREVERB_REFLECTIONS_PAN),
+    DECL(AL_EAXREVERB_LATE_REVERB_GAIN),
+    DECL(AL_EAXREVERB_LATE_REVERB_DELAY),
+    DECL(AL_EAXREVERB_LATE_REVERB_PAN),
+    DECL(AL_EAXREVERB_ECHO_TIME),
+    DECL(AL_EAXREVERB_ECHO_DEPTH),
+    DECL(AL_EAXREVERB_MODULATION_TIME),
+    DECL(AL_EAXREVERB_MODULATION_DEPTH),
+    DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF),
+    DECL(AL_EAXREVERB_HFREFERENCE),
+    DECL(AL_EAXREVERB_LFREFERENCE),
+    DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR),
+    DECL(AL_EAXREVERB_DECAY_HFLIMIT),
+
+    DECL(AL_REVERB_DENSITY),
+    DECL(AL_REVERB_DIFFUSION),
+    DECL(AL_REVERB_GAIN),
+    DECL(AL_REVERB_GAINHF),
+    DECL(AL_REVERB_DECAY_TIME),
+    DECL(AL_REVERB_DECAY_HFRATIO),
+    DECL(AL_REVERB_REFLECTIONS_GAIN),
+    DECL(AL_REVERB_REFLECTIONS_DELAY),
+    DECL(AL_REVERB_LATE_REVERB_GAIN),
+    DECL(AL_REVERB_LATE_REVERB_DELAY),
+    DECL(AL_REVERB_AIR_ABSORPTION_GAINHF),
+    DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR),
+    DECL(AL_REVERB_DECAY_HFLIMIT),
+
+    DECL(AL_CHORUS_WAVEFORM),
+    DECL(AL_CHORUS_PHASE),
+    DECL(AL_CHORUS_RATE),
+    DECL(AL_CHORUS_DEPTH),
+    DECL(AL_CHORUS_FEEDBACK),
+    DECL(AL_CHORUS_DELAY),
+
+    DECL(AL_DISTORTION_EDGE),
+    DECL(AL_DISTORTION_GAIN),
+    DECL(AL_DISTORTION_LOWPASS_CUTOFF),
+    DECL(AL_DISTORTION_EQCENTER),
+    DECL(AL_DISTORTION_EQBANDWIDTH),
+
+    DECL(AL_ECHO_DELAY),
+    DECL(AL_ECHO_LRDELAY),
+    DECL(AL_ECHO_DAMPING),
+    DECL(AL_ECHO_FEEDBACK),
+    DECL(AL_ECHO_SPREAD),
+
+    DECL(AL_FLANGER_WAVEFORM),
+    DECL(AL_FLANGER_PHASE),
+    DECL(AL_FLANGER_RATE),
+    DECL(AL_FLANGER_DEPTH),
+    DECL(AL_FLANGER_FEEDBACK),
+    DECL(AL_FLANGER_DELAY),
+
+    DECL(AL_FREQUENCY_SHIFTER_FREQUENCY),
+    DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION),
+    DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION),
+
+    DECL(AL_RING_MODULATOR_FREQUENCY),
+    DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF),
+    DECL(AL_RING_MODULATOR_WAVEFORM),
+
+    DECL(AL_PITCH_SHIFTER_COARSE_TUNE),
+    DECL(AL_PITCH_SHIFTER_FINE_TUNE),
+
+    DECL(AL_COMPRESSOR_ONOFF),
+
+    DECL(AL_EQUALIZER_LOW_GAIN),
+    DECL(AL_EQUALIZER_LOW_CUTOFF),
+    DECL(AL_EQUALIZER_MID1_GAIN),
+    DECL(AL_EQUALIZER_MID1_CENTER),
+    DECL(AL_EQUALIZER_MID1_WIDTH),
+    DECL(AL_EQUALIZER_MID2_GAIN),
+    DECL(AL_EQUALIZER_MID2_CENTER),
+    DECL(AL_EQUALIZER_MID2_WIDTH),
+    DECL(AL_EQUALIZER_HIGH_GAIN),
+    DECL(AL_EQUALIZER_HIGH_CUTOFF),
+
+    DECL(AL_DEDICATED_GAIN),
+
+    DECL(AL_AUTOWAH_ATTACK_TIME),
+    DECL(AL_AUTOWAH_RELEASE_TIME),
+    DECL(AL_AUTOWAH_RESONANCE),
+    DECL(AL_AUTOWAH_PEAK_GAIN),
+
+    DECL(AL_VOCAL_MORPHER_PHONEMEA),
+    DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING),
+    DECL(AL_VOCAL_MORPHER_PHONEMEB),
+    DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING),
+    DECL(AL_VOCAL_MORPHER_WAVEFORM),
+    DECL(AL_VOCAL_MORPHER_RATE),
+
+    DECL(AL_EFFECTSLOT_TARGET_SOFT),
+
+    DECL(AL_NUM_RESAMPLERS_SOFT),
+    DECL(AL_DEFAULT_RESAMPLER_SOFT),
+    DECL(AL_SOURCE_RESAMPLER_SOFT),
+    DECL(AL_RESAMPLER_NAME_SOFT),
+
+    DECL(AL_SOURCE_SPATIALIZE_SOFT),
+    DECL(AL_AUTO_SOFT),
+
+    DECL(AL_MAP_READ_BIT_SOFT),
+    DECL(AL_MAP_WRITE_BIT_SOFT),
+    DECL(AL_MAP_PERSISTENT_BIT_SOFT),
+    DECL(AL_PRESERVE_DATA_BIT_SOFT),
+
+    DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT),
+    DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT),
+    DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT),
+    DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT),
+    DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT),
+
+    DECL(AL_DROP_UNMATCHED_SOFT),
+    DECL(AL_REMIX_UNMATCHED_SOFT),
+
+    DECL(AL_AMBISONIC_LAYOUT_SOFT),
+    DECL(AL_AMBISONIC_SCALING_SOFT),
+    DECL(AL_FUMA_SOFT),
+    DECL(AL_ACN_SOFT),
+    DECL(AL_SN3D_SOFT),
+    DECL(AL_N3D_SOFT),
+
+    DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT),
+    DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT),
+
+    DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT),
+
+    DECL(AL_EFFECT_CONVOLUTION_REVERB_SOFT),
+    DECL(AL_EFFECTSLOT_STATE_SOFT),
+};
+#undef DECL
+
+constexpr ALCchar alcNoError[] = "No Error";
+constexpr ALCchar alcErrInvalidDevice[] = "Invalid Device";
+constexpr ALCchar alcErrInvalidContext[] = "Invalid Context";
+constexpr ALCchar alcErrInvalidEnum[] = "Invalid Enum";
+constexpr ALCchar alcErrInvalidValue[] = "Invalid Value";
+constexpr ALCchar alcErrOutOfMemory[] = "Out of Memory";
+
+
+/************************************************
+ * Global variables
+ ************************************************/
+
+/* Enumerated device names */
+constexpr ALCchar alcDefaultName[] = "OpenAL Soft\0";
+
+std::string alcAllDevicesList;
+std::string alcCaptureDeviceList;
+
+/* Default is always the first in the list */
+al::string alcDefaultAllDevicesSpecifier;
+al::string alcCaptureDefaultDeviceSpecifier;
+
+/* 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_SOFTX_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_SOFTX_filter_gain_ex "
+    "AL_SOFT_gain_clamp_ex "
+    "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";
+
+std::atomic<ALCenum> LastNullDeviceError{ALC_NO_ERROR};
+
+/* Thread-local current context. The handling may look a little obtuse, but
+ * it's designed this way to avoid a bug with 32-bit GCC/MinGW, which causes
+ * thread-local object destructors to get a junk 'this' pointer. This method
+ * has the benefit of making LocalContext access more efficient since it's a
+ * a plain pointer, with the ThreadContext object used to check it at thread
+ * exit (and given no data fields, 'this' being junk is inconsequential since
+ * it's never accessed).
+ */
+thread_local ALCcontext *LocalContext{nullptr};
+class ThreadCtx {
+public:
+    ~ThreadCtx()
+    {
+        if(ALCcontext *ctx{LocalContext})
+        {
+            const bool result{ctx->releaseIfNoDelete()};
+            ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
+                result ? "" : ", leak detected");
+        }
+    }
+
+    void set(ALCcontext *ctx) const noexcept { LocalContext = ctx; }
+};
+thread_local ThreadCtx ThreadContext;
+
+/* Process-wide current context */
+std::atomic<ALCcontext*> GlobalContext{nullptr};
+
+/* Flag to trap ALC device errors */
+bool TrapALCError{false};
+
+/* One-time configuration init control */
+std::once_flag alc_config_once{};
+
+/* Default effect that applies to sources that don't have an effect on send 0 */
+ALeffect DefaultEffect;
+
+/* Flag to specify if alcSuspendContext/alcProcessContext should defer/process
+ * updates.
+ */
+bool SuspendDefers{true};
+
+/* Initial seed for dithering. */
+constexpr uint DitherRNGSeed{22222u};
+
+
+/************************************************
+ * ALC information
+ ************************************************/
+constexpr ALCchar alcNoDeviceExtList[] =
+    "ALC_ENUMERATE_ALL_EXT "
+    "ALC_ENUMERATION_EXT "
+    "ALC_EXT_CAPTURE "
+    "ALC_EXT_thread_local_context "
+    "ALC_SOFT_loopback "
+    "ALC_SOFT_loopback_bformat";
+constexpr ALCchar alcExtensionList[] =
+    "ALC_ENUMERATE_ALL_EXT "
+    "ALC_ENUMERATION_EXT "
+    "ALC_EXT_CAPTURE "
+    "ALC_EXT_DEDICATED "
+    "ALC_EXT_disconnect "
+    "ALC_EXT_EFX "
+    "ALC_EXT_thread_local_context "
+    "ALC_SOFT_device_clock "
+    "ALC_SOFT_HRTF "
+    "ALC_SOFT_loopback "
+    "ALC_SOFT_loopback_bformat "
+    "ALC_SOFT_output_limiter "
+    "ALC_SOFT_pause_device";
+constexpr int alcMajorVersion{1};
+constexpr int alcMinorVersion{1};
+
+constexpr int alcEFXMajorVersion{1};
+constexpr int alcEFXMinorVersion{0};
+
+
+/* To avoid extraneous allocations, a 0-sized FlexArray<ALCcontext*> is defined
+ * globally as a sharable object.
+ */
+al::FlexArray<ALCcontext*> EmptyContextArray{0u};
+
+
+using DeviceRef = al::intrusive_ptr<ALCdevice>;
+
+
+/************************************************
+ * Device lists
+ ************************************************/
+al::vector<ALCdevice*> DeviceList;
+al::vector<ALCcontext*> ContextList;
+
+std::recursive_mutex ListLock;
+
+
+void alc_initconfig(void)
+{
+    if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL"))
+    {
+        long lvl = strtol(loglevel->c_str(), nullptr, 0);
+        if(lvl >= static_cast<long>(LogLevel::Trace))
+            gLogLevel = LogLevel::Trace;
+        else if(lvl <= static_cast<long>(LogLevel::Disable))
+            gLogLevel = LogLevel::Disable;
+        else
+            gLogLevel = static_cast<LogLevel>(lvl);
+    }
+
+#ifdef _WIN32
+    if(const auto logfile = al::getenv(L"ALSOFT_LOGFILE"))
+    {
+        FILE *logf{_wfopen(logfile->c_str(), L"wt")};
+        if(logf) gLogFile = logf;
+        else
+        {
+            auto u8name = wstr_to_utf8(logfile->c_str());
+            ERR("Failed to open log file '%s'\n", u8name.c_str());
+        }
+    }
+#else
+    if(const auto logfile = al::getenv("ALSOFT_LOGFILE"))
+    {
+        FILE *logf{fopen(logfile->c_str(), "wt")};
+        if(logf) gLogFile = logf;
+        else ERR("Failed to open log file '%s'\n", logfile->c_str());
+    }
+#endif
+
+    TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH,
+        ALSOFT_GIT_BRANCH);
+    {
+        al::string names;
+        if(al::size(BackendList) < 1)
+            names = "(none)";
+        else
+        {
+            const al::span<const BackendInfo> infos{BackendList};
+            names = infos[0].name;
+            for(const auto &backend : infos.subspan<1>())
+            {
+                names += ", ";
+                names += backend.name;
+            }
+        }
+        TRACE("Supported backends: %s\n", names.c_str());
+    }
+    ReadALConfig();
+
+    if(auto suspendmode = al::getenv("__ALSOFT_SUSPEND_CONTEXT"))
+    {
+        if(al::strcasecmp(suspendmode->c_str(), "ignore") == 0)
+        {
+            SuspendDefers = false;
+            TRACE("Selected context suspend behavior, \"ignore\"\n");
+        }
+        else
+            ERR("Unhandled context suspend behavior setting: \"%s\"\n", suspendmode->c_str());
+    }
+
+    int capfilter{0};
+#if defined(HAVE_SSE4_1)
+    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
+#elif defined(HAVE_SSE3)
+    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
+#elif defined(HAVE_SSE2)
+    capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2;
+#elif defined(HAVE_SSE)
+    capfilter |= CPU_CAP_SSE;
+#endif
+#ifdef HAVE_NEON
+    capfilter |= CPU_CAP_NEON;
+#endif
+    if(auto cpuopt = ConfigValueStr(nullptr, nullptr, "disable-cpu-exts"))
+    {
+        const char *str{cpuopt->c_str()};
+        if(al::strcasecmp(str, "all") == 0)
+            capfilter = 0;
+        else
+        {
+            const char *next = str;
+            do {
+                str = next;
+                while(isspace(str[0]))
+                    str++;
+                next = strchr(str, ',');
+
+                if(!str[0] || str[0] == ',')
+                    continue;
+
+                size_t len{next ? static_cast<size_t>(next-str) : strlen(str)};
+                while(len > 0 && isspace(str[len-1]))
+                    len--;
+                if(len == 3 && al::strncasecmp(str, "sse", len) == 0)
+                    capfilter &= ~CPU_CAP_SSE;
+                else if(len == 4 && al::strncasecmp(str, "sse2", len) == 0)
+                    capfilter &= ~CPU_CAP_SSE2;
+                else if(len == 4 && al::strncasecmp(str, "sse3", len) == 0)
+                    capfilter &= ~CPU_CAP_SSE3;
+                else if(len == 6 && al::strncasecmp(str, "sse4.1", len) == 0)
+                    capfilter &= ~CPU_CAP_SSE4_1;
+                else if(len == 4 && al::strncasecmp(str, "neon", len) == 0)
+                    capfilter &= ~CPU_CAP_NEON;
+                else
+                    WARN("Invalid CPU extension \"%s\"\n", str);
+            } while(next++);
+        }
+    }
+    if(auto cpuopt = GetCPUInfo())
+    {
+        if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty())
+        {
+            TRACE("Vendor ID: \"%s\"\n", cpuopt->mVendor.c_str());
+            TRACE("Name: \"%s\"\n", cpuopt->mName.c_str());
+        }
+        const int caps{cpuopt->mCaps};
+        TRACE("Extensions:%s%s%s%s%s%s\n",
+            ((capfilter&CPU_CAP_SSE)    ? ((caps&CPU_CAP_SSE)    ? " +SSE"    : " -SSE")    : ""),
+            ((capfilter&CPU_CAP_SSE2)   ? ((caps&CPU_CAP_SSE2)   ? " +SSE2"   : " -SSE2")   : ""),
+            ((capfilter&CPU_CAP_SSE3)   ? ((caps&CPU_CAP_SSE3)   ? " +SSE3"   : " -SSE3")   : ""),
+            ((capfilter&CPU_CAP_SSE4_1) ? ((caps&CPU_CAP_SSE4_1) ? " +SSE4.1" : " -SSE4.1") : ""),
+            ((capfilter&CPU_CAP_NEON)   ? ((caps&CPU_CAP_NEON)   ? " +NEON"   : " -NEON")   : ""),
+            ((!capfilter) ? " -none-" : ""));
+        CPUCapFlags = caps & capfilter;
+    }
+
+    if(auto priopt = ConfigValueInt(nullptr, nullptr, "rt-prio"))
+        RTPrioLevel = *priopt;
+
+    aluInit();
+    aluInitMixer();
+
+    auto traperr = al::getenv("ALSOFT_TRAP_ERROR");
+    if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0
+            || std::strtol(traperr->c_str(), nullptr, 0) == 1))
+    {
+        TrapALError  = true;
+        TrapALCError = true;
+    }
+    else
+    {
+        traperr = al::getenv("ALSOFT_TRAP_AL_ERROR");
+        if(traperr)
+            TrapALError = al::strcasecmp(traperr->c_str(), "true") == 0
+                || strtol(traperr->c_str(), nullptr, 0) == 1;
+        else
+            TrapALError = !!GetConfigValueBool(nullptr, nullptr, "trap-al-error", false);
+
+        traperr = al::getenv("ALSOFT_TRAP_ALC_ERROR");
+        if(traperr)
+            TrapALCError = al::strcasecmp(traperr->c_str(), "true") == 0
+                || strtol(traperr->c_str(), nullptr, 0) == 1;
+        else
+            TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", false);
+    }
+
+    if(auto boostopt = ConfigValueFloat(nullptr, "reverb", "boost"))
+    {
+        const float valf{std::isfinite(*boostopt) ? clampf(*boostopt, -24.0f, 24.0f) : 0.0f};
+        ReverbBoost *= std::pow(10.0f, valf / 20.0f);
+    }
+
+    auto BackendListEnd = std::end(BackendList);
+    auto devopt = al::getenv("ALSOFT_DRIVERS");
+    if(devopt || (devopt=ConfigValueStr(nullptr, nullptr, "drivers")))
+    {
+        auto backendlist_cur = std::begin(BackendList);
+
+        bool endlist{true};
+        const char *next{devopt->c_str()};
+        do {
+            const char *devs{next};
+            while(isspace(devs[0]))
+                devs++;
+            next = strchr(devs, ',');
+
+            const bool delitem{devs[0] == '-'};
+            if(devs[0] == '-') devs++;
+
+            if(!devs[0] || devs[0] == ',')
+            {
+                endlist = false;
+                continue;
+            }
+            endlist = true;
+
+            size_t len{next ? (static_cast<size_t>(next-devs)) : strlen(devs)};
+            while(len > 0 && isspace(devs[len-1])) --len;
+#ifdef HAVE_WASAPI
+            /* HACK: For backwards compatibility, convert backend references of
+             * mmdevapi to wasapi. This should eventually be removed.
+             */
+            if(len == 8 && strncmp(devs, "mmdevapi", len) == 0)
+            {
+                devs = "wasapi";
+                len = 6;
+            }
+#endif
+
+            auto find_backend = [devs,len](const BackendInfo &backend) -> bool
+            { return len == strlen(backend.name) && strncmp(backend.name, devs, len) == 0; };
+            auto this_backend = std::find_if(std::begin(BackendList), BackendListEnd,
+                find_backend);
+
+            if(this_backend == BackendListEnd)
+                continue;
+
+            if(delitem)
+                BackendListEnd = std::move(this_backend+1, BackendListEnd, this_backend);
+            else
+                backendlist_cur = std::rotate(backendlist_cur, this_backend, this_backend+1);
+        } while(next++);
+
+        if(endlist)
+            BackendListEnd = backendlist_cur;
+    }
+
+    auto init_backend = [](BackendInfo &backend) -> void
+    {
+        if(PlaybackFactory && CaptureFactory)
+            return;
+
+        BackendFactory &factory = backend.getFactory();
+        if(!factory.init())
+        {
+            WARN("Failed to initialize backend \"%s\"\n", backend.name);
+            return;
+        }
+
+        TRACE("Initialized backend \"%s\"\n", backend.name);
+        if(!PlaybackFactory && factory.querySupport(BackendType::Playback))
+        {
+            PlaybackFactory = &factory;
+            TRACE("Added \"%s\" for playback\n", backend.name);
+        }
+        if(!CaptureFactory && factory.querySupport(BackendType::Capture))
+        {
+            CaptureFactory = &factory;
+            TRACE("Added \"%s\" for capture\n", backend.name);
+        }
+    };
+    std::for_each(std::begin(BackendList), BackendListEnd, init_backend);
+
+    LoopbackBackendFactory::getFactory().init();
+
+    if(!PlaybackFactory)
+        WARN("No playback backend available!\n");
+    if(!CaptureFactory)
+        WARN("No capture backend available!\n");
+
+    if(auto exclopt = ConfigValueStr(nullptr, nullptr, "excludefx"))
+    {
+        const char *next{exclopt->c_str()};
+        do {
+            const char *str{next};
+            next = strchr(str, ',');
+
+            if(!str[0] || next == str)
+                continue;
+
+            size_t len{next ? static_cast<size_t>(next-str) : strlen(str)};
+            for(const EffectList &effectitem : gEffectList)
+            {
+                if(len == strlen(effectitem.name) &&
+                   strncmp(effectitem.name, str, len) == 0)
+                    DisabledEffects[effectitem.type] = true;
+            }
+        } while(next++);
+    }
+
+    InitEffect(&DefaultEffect);
+    auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB");
+    if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb")))
+        LoadReverbPreset(defrevopt->c_str(), &DefaultEffect);
+}
+#define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();})
+
+
+/************************************************
+ * Device enumeration
+ ************************************************/
+void ProbeAllDevicesList()
+{
+    DO_INITCONFIG();
+
+    std::lock_guard<std::recursive_mutex> _{ListLock};
+    if(!PlaybackFactory)
+        decltype(alcAllDevicesList){}.swap(alcAllDevicesList);
+    else
+    {
+        std::string names{PlaybackFactory->probe(BackendType::Playback)};
+        if(names.empty()) names += '\0';
+        names.swap(alcAllDevicesList);
+    }
+}
+void ProbeCaptureDeviceList()
+{
+    DO_INITCONFIG();
+
+    std::lock_guard<std::recursive_mutex> _{ListLock};
+    if(!CaptureFactory)
+        decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList);
+    else
+    {
+        std::string names{CaptureFactory->probe(BackendType::Capture)};
+        if(names.empty()) names += '\0';
+        names.swap(alcCaptureDeviceList);
+    }
+}
+
+} // namespace
+
+/* Mixing thread piority level */
+int RTPrioLevel{1};
+
+FILE *gLogFile{stderr};
+#ifdef _DEBUG
+LogLevel gLogLevel{LogLevel::Warning};
+#else
+LogLevel gLogLevel{LogLevel::Error};
+#endif
+
+/************************************************
+ * Library initialization
+ ************************************************/
+#if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC)
+BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/)
+{
+    switch(reason)
+    {
+    case DLL_PROCESS_ATTACH:
+        /* Pin the DLL so we won't get unloaded until the process terminates */
+        GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+            reinterpret_cast<WCHAR*>(module), &module);
+        break;
+    }
+    return TRUE;
+}
+#endif
+
+/************************************************
+ * Device format information
+ ************************************************/
+namespace {
+
+struct DevFmtPair { DevFmtChannels chans; DevFmtType type; };
+al::optional<DevFmtPair> DecomposeDevFormat(ALenum format)
+{
+    static const struct {
+        ALenum format;
+        DevFmtChannels channels;
+        DevFmtType type;
+    } list[] = {
+        { AL_FORMAT_MONO8,        DevFmtMono, DevFmtUByte },
+        { AL_FORMAT_MONO16,       DevFmtMono, DevFmtShort },
+        { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat },
+
+        { AL_FORMAT_STEREO8,        DevFmtStereo, DevFmtUByte },
+        { AL_FORMAT_STEREO16,       DevFmtStereo, DevFmtShort },
+        { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat },
+
+        { AL_FORMAT_QUAD8,  DevFmtQuad, DevFmtUByte },
+        { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort },
+        { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat },
+
+        { AL_FORMAT_51CHN8,  DevFmtX51, DevFmtUByte },
+        { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort },
+        { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat },
+
+        { AL_FORMAT_61CHN8,  DevFmtX61, DevFmtUByte },
+        { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort },
+        { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat },
+
+        { AL_FORMAT_71CHN8,  DevFmtX71, DevFmtUByte },
+        { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort },
+        { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat },
+    };
+
+    for(const auto &item : list)
+    {
+        if(item.format == format)
+            return al::make_optional(DevFmtPair{item.channels, item.type});
+    }
+
+    return al::nullopt;
+}
+
+al::optional<DevFmtType> DevFmtTypeFromEnum(ALCenum type)
+{
+    switch(type)
+    {
+    case ALC_BYTE_SOFT: return al::make_optional(DevFmtByte);
+    case ALC_UNSIGNED_BYTE_SOFT: return al::make_optional(DevFmtUByte);
+    case ALC_SHORT_SOFT: return al::make_optional(DevFmtShort);
+    case ALC_UNSIGNED_SHORT_SOFT: return al::make_optional(DevFmtUShort);
+    case ALC_INT_SOFT: return al::make_optional(DevFmtInt);
+    case ALC_UNSIGNED_INT_SOFT: return al::make_optional(DevFmtUInt);
+    case ALC_FLOAT_SOFT: return al::make_optional(DevFmtFloat);
+    }
+    WARN("Unsupported format type: 0x%04x\n", type);
+    return al::nullopt;
+}
+ALCenum EnumFromDevFmt(DevFmtType type)
+{
+    switch(type)
+    {
+    case DevFmtByte: return ALC_BYTE_SOFT;
+    case DevFmtUByte: return ALC_UNSIGNED_BYTE_SOFT;
+    case DevFmtShort: return ALC_SHORT_SOFT;
+    case DevFmtUShort: return ALC_UNSIGNED_SHORT_SOFT;
+    case DevFmtInt: return ALC_INT_SOFT;
+    case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT;
+    case DevFmtFloat: return ALC_FLOAT_SOFT;
+    }
+    throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))};
+}
+
+al::optional<DevFmtChannels> DevFmtChannelsFromEnum(ALCenum channels)
+{
+    switch(channels)
+    {
+    case ALC_MONO_SOFT: return al::make_optional(DevFmtMono);
+    case ALC_STEREO_SOFT: return al::make_optional(DevFmtStereo);
+    case ALC_QUAD_SOFT: return al::make_optional(DevFmtQuad);
+    case ALC_5POINT1_SOFT: return al::make_optional(DevFmtX51);
+    case ALC_6POINT1_SOFT: return al::make_optional(DevFmtX61);
+    case ALC_7POINT1_SOFT: return al::make_optional(DevFmtX71);
+    case ALC_BFORMAT3D_SOFT: return al::make_optional(DevFmtAmbi3D);
+    }
+    WARN("Unsupported format channels: 0x%04x\n", channels);
+    return al::nullopt;
+}
+ALCenum EnumFromDevFmt(DevFmtChannels channels)
+{
+    switch(channels)
+    {
+    case DevFmtMono: return ALC_MONO_SOFT;
+    case DevFmtStereo: return ALC_STEREO_SOFT;
+    case DevFmtQuad: return ALC_QUAD_SOFT;
+    case DevFmtX51: /* fall-through */
+    case DevFmtX51Rear: return ALC_5POINT1_SOFT;
+    case DevFmtX61: return ALC_6POINT1_SOFT;
+    case DevFmtX71: return ALC_7POINT1_SOFT;
+    case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT;
+    }
+    throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))};
+}
+
+al::optional<DevAmbiLayout> DevAmbiLayoutFromEnum(ALCenum layout)
+{
+    switch(layout)
+    {
+    case ALC_FUMA_SOFT: return al::make_optional(DevAmbiLayout::FuMa);
+    case ALC_ACN_SOFT: return al::make_optional(DevAmbiLayout::ACN);
+    }
+    WARN("Unsupported ambisonic layout: 0x%04x\n", layout);
+    return al::nullopt;
+}
+ALCenum EnumFromDevAmbi(DevAmbiLayout layout)
+{
+    switch(layout)
+    {
+    case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT;
+    case DevAmbiLayout::ACN: return ALC_ACN_SOFT;
+    }
+    throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))};
+}
+
+al::optional<DevAmbiScaling> DevAmbiScalingFromEnum(ALCenum scaling)
+{
+    switch(scaling)
+    {
+    case ALC_FUMA_SOFT: return al::make_optional(DevAmbiScaling::FuMa);
+    case ALC_SN3D_SOFT: return al::make_optional(DevAmbiScaling::SN3D);
+    case ALC_N3D_SOFT: return al::make_optional(DevAmbiScaling::N3D);
+    }
+    WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling);
+    return al::nullopt;
+}
+ALCenum EnumFromDevAmbi(DevAmbiScaling scaling)
+{
+    switch(scaling)
+    {
+    case DevAmbiScaling::FuMa: return ALC_FUMA_SOFT;
+    case DevAmbiScaling::SN3D: return ALC_SN3D_SOFT;
+    case DevAmbiScaling::N3D: return ALC_N3D_SOFT;
+    }
+    throw std::runtime_error{"Invalid DevAmbiScaling: "+std::to_string(int(scaling))};
+}
+
+
+/* Downmixing channel arrays, to map the given format's missing channels to
+ * existing ones. Based on Wine's DSound downmix values, which are based on
+ * PulseAudio's.
+ */
+const std::array<InputRemixMap,7> MonoDownmix{{
+    { FrontLeft,   {{{FrontCenter, 0.5f},      {LFE, 0.0f}}} },
+    { FrontRight,  {{{FrontCenter, 0.5f},      {LFE, 0.0f}}} },
+    { SideLeft,    {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} },
+    { SideRight,   {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} },
+    { BackLeft,    {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} },
+    { BackRight,   {{{FrontCenter, 0.5f/9.0f}, {LFE, 0.0f}}} },
+    { BackCenter,  {{{FrontCenter, 1.0f/9.0f}, {LFE, 0.0f}}} },
+}};
+const std::array<InputRemixMap,6> StereoDownmix{{
+    { FrontCenter, {{{FrontLeft, 0.5f},      {FrontRight, 0.5f}}} },
+    { SideLeft,    {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} },
+    { SideRight,   {{{FrontLeft, 0.0f},      {FrontRight, 1.0f/9.0f}}} },
+    { BackLeft,    {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} },
+    { BackRight,   {{{FrontLeft, 0.0f},      {FrontRight, 1.0f/9.0f}}} },
+    { BackCenter,  {{{FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f}}} },
+}};
+const std::array<InputRemixMap,4> QuadDownmix{{
+    { FrontCenter, {{{FrontLeft,  0.5f}, {FrontRight, 0.5f}}} },
+    { SideLeft,    {{{FrontLeft,  0.5f}, {BackLeft,   0.5f}}} },
+    { SideRight,   {{{FrontRight, 0.5f}, {BackRight,  0.5f}}} },
+    { BackCenter,  {{{BackLeft,   0.5f}, {BackRight,  0.5f}}} },
+}};
+const std::array<InputRemixMap,3> X51Downmix{{
+    { BackLeft,   {{{SideLeft, 1.0f}, {SideRight, 0.0f}}} },
+    { BackRight,  {{{SideLeft, 0.0f}, {SideRight, 1.0f}}} },
+    { BackCenter, {{{SideLeft, 0.5f}, {SideRight, 0.5f}}} },
+}};
+const std::array<InputRemixMap,3> X51RearDownmix{{
+    { SideLeft,   {{{BackLeft, 1.0f}, {BackRight, 0.0f}}} },
+    { SideRight,  {{{BackLeft, 0.0f}, {BackRight, 1.0f}}} },
+    { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} },
+}};
+const std::array<InputRemixMap,2> X61Downmix{{
+    { BackLeft,  {{{BackCenter, 0.5f}, {SideLeft,  0.5f}}} },
+    { BackRight, {{{BackCenter, 0.5f}, {SideRight, 0.5f}}} },
+}};
+const std::array<InputRemixMap,1> X71Downmix{{
+    { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} },
+}};
+
+} // namespace
+
+/************************************************
+ * Miscellaneous ALC helpers
+ ************************************************/
+
+void ALCcontext::processUpdates()
+{
+    std::lock_guard<std::mutex> _{mPropLock};
+    if(mDeferUpdates.exchange(false, std::memory_order_acq_rel))
+    {
+        /* Tell the mixer to stop applying updates, then wait for any active
+         * updating to finish, before providing updates.
+         */
+        mHoldUpdates.store(true, std::memory_order_release);
+        while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
+            /* busy-wait */
+        }
+
+        if(!mPropsClean.test_and_set(std::memory_order_acq_rel))
+            UpdateContextProps(this);
+        if(!mListener.PropsClean.test_and_set(std::memory_order_acq_rel))
+            UpdateListenerProps(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);
+    }
+}
+
+
+void ALCcontext::allocVoiceChanges(size_t addcount)
+{
+    constexpr size_t clustersize{128};
+    /* Convert element count to cluster count. */
+    addcount = (addcount+(clustersize-1)) / clustersize;
+    while(addcount)
+    {
+        VoiceChangeCluster cluster{std::make_unique<VoiceChange[]>(clustersize)};
+        for(size_t i{1};i < clustersize;++i)
+            cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed);
+        cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed);
+        mVoiceChangeClusters.emplace_back(std::move(cluster));
+        mVoiceChangeTail = mVoiceChangeClusters.back().get();
+        --addcount;
+    }
+}
+
+void ALCcontext::allocVoices(size_t addcount)
+{
+    constexpr size_t clustersize{32};
+    /* Convert element count to cluster count. */
+    addcount = (addcount+(clustersize-1)) / clustersize;
+
+    if(addcount >= std::numeric_limits<int>::max()/clustersize - mVoiceClusters.size())
+        throw std::runtime_error{"Allocating too many voices"};
+    const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize};
+    TRACE("Increasing allocated voices to %zu\n", totalcount);
+
+    auto newarray = VoiceArray::Create(totalcount);
+    while(addcount)
+    {
+        mVoiceClusters.emplace_back(std::make_unique<Voice[]>(clustersize));
+        --addcount;
+    }
+
+    auto voice_iter = newarray->begin();
+    for(VoiceCluster &cluster : mVoiceClusters)
+    {
+        for(size_t i{0};i < clustersize;++i)
+            *(voice_iter++) = &cluster[i];
+    }
+
+    if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel))
+    {
+        mDevice->waitForMix();
+        delete oldvoices;
+    }
+}
+
+
+/** Stores the latest ALC device error. */
+static void alcSetError(ALCdevice *device, ALCenum errorCode)
+{
+    WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode);
+    if(TrapALCError)
+    {
+#ifdef _WIN32
+        /* DebugBreak() will cause an exception if there is no debugger */
+        if(IsDebuggerPresent())
+            DebugBreak();
+#elif defined(SIGTRAP)
+        raise(SIGTRAP);
+#endif
+    }
+
+    if(device)
+        device->LastError.store(errorCode);
+    else
+        LastNullDeviceError.store(errorCode);
+}
+
+
+static std::unique_ptr<Compressor> CreateDeviceLimiter(const ALCdevice *device, const float threshold)
+{
+    constexpr bool AutoKnee{true};
+    constexpr bool AutoAttack{true};
+    constexpr bool AutoRelease{true};
+    constexpr bool AutoPostGain{true};
+    constexpr bool AutoDeclip{true};
+    constexpr float LookAheadTime{0.001f};
+    constexpr float HoldTime{0.002f};
+    constexpr float PreGainDb{0.0f};
+    constexpr float PostGainDb{0.0f};
+    constexpr float Ratio{std::numeric_limits<float>::infinity()};
+    constexpr float KneeDb{0.0f};
+    constexpr float AttackTime{0.02f};
+    constexpr float ReleaseTime{0.2f};
+
+    return Compressor::Create(device->RealOut.Buffer.size(), static_cast<float>(device->Frequency),
+        AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, LookAheadTime, HoldTime,
+        PreGainDb, PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime);
+}
+
+/**
+ * Updates the device's base clock time with however many samples have been
+ * done. This is used so frequency changes on the device don't cause the time
+ * to jump forward or back. Must not be called while the device is running/
+ * mixing.
+ */
+static inline void UpdateClockBase(ALCdevice *device)
+{
+    IncrementRef(device->MixCount);
+    device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
+    device->SamplesDone = 0;
+    IncrementRef(device->MixCount);
+}
+
+/**
+ * Updates device parameters according to the attribute list (caller is
+ * responsible for holding the list lock).
+ */
+static ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList)
+{
+    HrtfRequestMode hrtf_userreq{Hrtf_Default};
+    HrtfRequestMode hrtf_appreq{Hrtf_Default};
+    ALCenum gainLimiter{device->LimiterState};
+    uint new_sends{device->NumAuxSends};
+    DevFmtChannels oldChans;
+    DevFmtType oldType;
+    int hrtf_id{-1};
+    uint oldFreq;
+
+    if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback)
+    {
+        WARN("Missing attributes for loopback device\n");
+        return ALC_INVALID_VALUE;
+    }
+
+    // Check for attributes
+    if(attrList && attrList[0])
+    {
+        uint numMono{device->NumMonoSources};
+        uint numStereo{device->NumStereoSources};
+        uint numSends{device->NumAuxSends};
+
+        al::optional<DevFmtChannels> optchans;
+        al::optional<DevFmtType> opttype;
+        al::optional<DevAmbiLayout> optlayout;
+        al::optional<DevAmbiScaling> optscale;
+
+        uint aorder{0u};
+        uint freq{0u};
+
+#define TRACE_ATTR(a, v) TRACE("%s = %d\n", #a, v)
+        size_t attrIdx{0};
+        while(attrList[attrIdx])
+        {
+            switch(attrList[attrIdx])
+            {
+            case ALC_FORMAT_CHANNELS_SOFT:
+                TRACE_ATTR(ALC_FORMAT_CHANNELS_SOFT, attrList[attrIdx + 1]);
+                optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]);
+                break;
+
+            case ALC_FORMAT_TYPE_SOFT:
+                TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, attrList[attrIdx + 1]);
+                opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]);
+                break;
+
+            case ALC_FREQUENCY:
+                freq = static_cast<uint>(attrList[attrIdx + 1]);
+                TRACE_ATTR(ALC_FREQUENCY, freq);
+                break;
+
+            case ALC_AMBISONIC_LAYOUT_SOFT:
+                TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, attrList[attrIdx + 1]);
+                optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]);
+                break;
+
+            case ALC_AMBISONIC_SCALING_SOFT:
+                TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, attrList[attrIdx + 1]);
+                optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]);
+                break;
+
+            case ALC_AMBISONIC_ORDER_SOFT:
+                aorder = static_cast<uint>(attrList[attrIdx + 1]);
+                TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder);
+                break;
+
+            case ALC_MONO_SOURCES:
+                numMono = static_cast<uint>(attrList[attrIdx + 1]);
+                TRACE_ATTR(ALC_MONO_SOURCES, numMono);
+                if(numMono > INT_MAX) numMono = 0;
+                break;
+
+            case ALC_STEREO_SOURCES:
+                numStereo = static_cast<uint>(attrList[attrIdx + 1]);
+                TRACE_ATTR(ALC_STEREO_SOURCES, numStereo);
+                if(numStereo > INT_MAX) numStereo = 0;
+                break;
+
+            case ALC_MAX_AUXILIARY_SENDS:
+                numSends = static_cast<uint>(attrList[attrIdx + 1]);
+                TRACE_ATTR(ALC_MAX_AUXILIARY_SENDS, numSends);
+                if(numSends > INT_MAX) numSends = 0;
+                else numSends = minu(numSends, MAX_SENDS);
+                break;
+
+            case ALC_HRTF_SOFT:
+                TRACE_ATTR(ALC_HRTF_SOFT, attrList[attrIdx + 1]);
+                if(attrList[attrIdx + 1] == ALC_FALSE)
+                    hrtf_appreq = Hrtf_Disable;
+                else if(attrList[attrIdx + 1] == ALC_TRUE)
+                    hrtf_appreq = Hrtf_Enable;
+                else
+                    hrtf_appreq = Hrtf_Default;
+                break;
+
+            case ALC_HRTF_ID_SOFT:
+                hrtf_id = attrList[attrIdx + 1];
+                TRACE_ATTR(ALC_HRTF_ID_SOFT, hrtf_id);
+                break;
+
+            case ALC_OUTPUT_LIMITER_SOFT:
+                gainLimiter = attrList[attrIdx + 1];
+                TRACE_ATTR(ALC_OUTPUT_LIMITER_SOFT, gainLimiter);
+                break;
+
+            default:
+                TRACE("0x%04X = %d (0x%x)\n", attrList[attrIdx],
+                    attrList[attrIdx + 1], attrList[attrIdx + 1]);
+                break;
+            }
+
+            attrIdx += 2;
+        }
+#undef TRACE_ATTR
+
+        const bool loopback{device->Type == DeviceType::Loopback};
+        if(loopback)
+        {
+            if(!optchans || !opttype)
+                return ALC_INVALID_VALUE;
+            if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE)
+                return ALC_INVALID_VALUE;
+            if(*optchans == DevFmtAmbi3D)
+            {
+                if(!optlayout || !optscale)
+                    return ALC_INVALID_VALUE;
+                if(aorder < 1 || aorder > MaxAmbiOrder)
+                    return ALC_INVALID_VALUE;
+                if((*optlayout == DevAmbiLayout::FuMa || *optscale == DevAmbiScaling::FuMa)
+                    && aorder > 3)
+                    return ALC_INVALID_VALUE;
+            }
+        }
+
+        /* If a context is already running on the device, stop playback so the
+         * device attributes can be updated.
+         */
+        if(device->Flags.test(DeviceRunning))
+            device->Backend->stop();
+        device->Flags.reset(DeviceRunning);
+
+        UpdateClockBase(device);
+
+        const char *devname{nullptr};
+        if(loopback)
+        {
+            device->Frequency = freq;
+            device->FmtChans = *optchans;
+            device->FmtType = *opttype;
+            if(device->FmtChans == DevFmtAmbi3D)
+            {
+                device->mAmbiOrder = aorder;
+                device->mAmbiLayout = *optlayout;
+                device->mAmbiScale = *optscale;
+            }
+        }
+        else
+        {
+            devname = device->DeviceName.c_str();
+
+            device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES;
+            device->UpdateSize = DEFAULT_UPDATE_SIZE;
+            device->Frequency = DEFAULT_OUTPUT_RATE;
+
+            freq = ConfigValueUInt(devname, nullptr, "frequency").value_or(freq);
+            if(freq < 1)
+                device->Flags.reset(FrequencyRequest);
+            else
+            {
+                freq = clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE);
+
+                const double scale{static_cast<double>(freq) / device->Frequency};
+                device->UpdateSize = static_cast<uint>(device->UpdateSize*scale + 0.5);
+                device->BufferSize = static_cast<uint>(device->BufferSize*scale + 0.5);
+
+                device->Frequency = freq;
+                device->Flags.set(FrequencyRequest);
+            }
+
+            if(auto persizeopt = ConfigValueUInt(devname, nullptr, "period_size"))
+                device->UpdateSize = clampu(*persizeopt, 64, 8192);
+
+            if(auto peropt = ConfigValueUInt(devname, nullptr, "periods"))
+                device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16);
+            else
+                device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2);
+        }
+
+        if(numMono > INT_MAX-numStereo)
+            numMono = INT_MAX-numStereo;
+        numMono += numStereo;
+        if(auto srcsopt = ConfigValueUInt(devname, nullptr, "sources"))
+        {
+            if(*srcsopt <= 0) numMono = 256;
+            else numMono = *srcsopt;
+        }
+        else
+            numMono = maxu(numMono, 256);
+        numStereo = minu(numStereo, numMono);
+        numMono -= numStereo;
+        device->SourcesMax = numMono + numStereo;
+
+        device->NumMonoSources = numMono;
+        device->NumStereoSources = numStereo;
+
+        if(auto sendsopt = ConfigValueInt(devname, nullptr, "sends"))
+            new_sends = minu(numSends, static_cast<uint>(clampi(*sendsopt, 0, MAX_SENDS)));
+        else
+            new_sends = numSends;
+    }
+
+    if(device->Flags.test(DeviceRunning))
+        return ALC_NO_ERROR;
+
+    device->AvgSpeakerDist = 0.0f;
+    device->Uhj_Encoder = nullptr;
+    device->AmbiDecoder = nullptr;
+    device->Bs2b = nullptr;
+    device->PostProcess = nullptr;
+
+    device->Limiter = nullptr;
+    device->ChannelDelays = nullptr;
+
+    std::fill(std::begin(device->HrtfAccumData), std::end(device->HrtfAccumData), float2{});
+
+    device->Dry.AmbiMap.fill(BFChannelConfig{});
+    device->Dry.Buffer = {};
+    std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u);
+    device->RealOut.RemixMap = {};
+    device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+    device->RealOut.Buffer = {};
+    device->MixBuffer.clear();
+    device->MixBuffer.shrink_to_fit();
+
+    UpdateClockBase(device);
+    device->FixedLatency = nanoseconds::zero();
+
+    device->DitherDepth = 0.0f;
+    device->DitherSeed = DitherRNGSeed;
+
+    /*************************************************************************
+     * Update device format request if HRTF is requested
+     */
+    device->HrtfStatus = ALC_HRTF_DISABLED_SOFT;
+    if(device->Type != DeviceType::Loopback)
+    {
+        if(auto hrtfopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf"))
+        {
+            const char *hrtf{hrtfopt->c_str()};
+            if(al::strcasecmp(hrtf, "true") == 0)
+                hrtf_userreq = Hrtf_Enable;
+            else if(al::strcasecmp(hrtf, "false") == 0)
+                hrtf_userreq = Hrtf_Disable;
+            else if(al::strcasecmp(hrtf, "auto") != 0)
+                ERR("Unexpected hrtf value: %s\n", hrtf);
+        }
+
+        if(hrtf_userreq == Hrtf_Enable || (hrtf_userreq != Hrtf_Disable && hrtf_appreq == Hrtf_Enable))
+        {
+            device->FmtChans = DevFmtStereo;
+            device->Flags.set(ChannelsRequest);
+        }
+    }
+
+    oldFreq  = device->Frequency;
+    oldChans = device->FmtChans;
+    oldType  = device->FmtType;
+
+    TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n",
+        device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans),
+        device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType),
+        device->Flags.test(FrequencyRequest)?"*":"", device->Frequency,
+        device->UpdateSize, device->BufferSize);
+
+    try {
+        auto backend = device->Backend.get();
+        if(!backend->reset())
+            throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"};
+    }
+    catch(std::exception &e) {
+        device->handleDisconnect("%s", e.what());
+        return ALC_INVALID_DEVICE;
+    }
+
+    if(device->FmtChans != oldChans && device->Flags.test(ChannelsRequest))
+    {
+        ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans),
+            DevFmtChannelsString(device->FmtChans));
+        device->Flags.reset(ChannelsRequest);
+    }
+    if(device->FmtType != oldType && device->Flags.test(SampleTypeRequest))
+    {
+        ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType),
+            DevFmtTypeString(device->FmtType));
+        device->Flags.reset(SampleTypeRequest);
+    }
+    if(device->Frequency != oldFreq && device->Flags.test(FrequencyRequest))
+    {
+        WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency);
+        device->Flags.reset(FrequencyRequest);
+    }
+
+    TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n",
+        DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
+        device->Frequency, device->UpdateSize, device->BufferSize);
+
+    switch(device->FmtChans)
+    {
+    case DevFmtMono: device->RealOut.RemixMap = MonoDownmix; break;
+    case DevFmtStereo: device->RealOut.RemixMap = StereoDownmix; break;
+    case DevFmtQuad: device->RealOut.RemixMap = QuadDownmix; break;
+    case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break;
+    case DevFmtX51Rear: device->RealOut.RemixMap = X51RearDownmix; break;
+    case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break;
+    case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break;
+    case DevFmtAmbi3D: break;
+    }
+
+    aluInitRenderer(device, hrtf_id, hrtf_appreq, hrtf_userreq);
+
+    device->NumAuxSends = new_sends;
+    TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n",
+        device->SourcesMax, device->NumMonoSources, device->NumStereoSources,
+        device->AuxiliaryEffectSlotMax, device->NumAuxSends);
+
+    nanoseconds::rep sample_delay{0};
+    if(device->Uhj_Encoder)
+        sample_delay += Uhj2Encoder::sFilterSize;
+    if(device->mHrtfState)
+        sample_delay += HrtfDirectDelay;
+    if(auto *ambidec = device->AmbiDecoder.get())
+    {
+        if(ambidec->hasStablizer())
+            sample_delay += FrontStablizer::DelayLength;
+    }
+
+    if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1))
+    {
+        int depth{ConfigValueInt(device->DeviceName.c_str(), nullptr, "dither-depth").value_or(0)};
+        if(depth <= 0)
+        {
+            switch(device->FmtType)
+            {
+            case DevFmtByte:
+            case DevFmtUByte:
+                depth = 8;
+                break;
+            case DevFmtShort:
+            case DevFmtUShort:
+                depth = 16;
+                break;
+            case DevFmtInt:
+            case DevFmtUInt:
+            case DevFmtFloat:
+                break;
+            }
+        }
+
+        if(depth > 0)
+        {
+            depth = clampi(depth, 2, 24);
+            device->DitherDepth = std::pow(2.0f, static_cast<float>(depth-1));
+        }
+    }
+    if(!(device->DitherDepth > 0.0f))
+        TRACE("Dithering disabled\n");
+    else
+        TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1,
+              device->DitherDepth);
+
+    device->LimiterState = gainLimiter;
+    if(auto limopt = ConfigValueBool(device->DeviceName.c_str(), nullptr, "output-limiter"))
+        gainLimiter = *limopt ? ALC_TRUE : ALC_FALSE;
+
+    /* Valid values for gainLimiter are ALC_DONT_CARE_SOFT, ALC_TRUE, and
+     * ALC_FALSE. For ALC_DONT_CARE_SOFT, use the limiter for integer-based
+     * output (where samples must be clamped), and don't for floating-point
+     * (which can take unclamped samples).
+     */
+    if(gainLimiter == ALC_DONT_CARE_SOFT)
+    {
+        switch(device->FmtType)
+        {
+            case DevFmtByte:
+            case DevFmtUByte:
+            case DevFmtShort:
+            case DevFmtUShort:
+            case DevFmtInt:
+            case DevFmtUInt:
+                gainLimiter = ALC_TRUE;
+                break;
+            case DevFmtFloat:
+                gainLimiter = ALC_FALSE;
+                break;
+        }
+    }
+    if(gainLimiter == ALC_FALSE)
+        TRACE("Output limiter disabled\n");
+    else
+    {
+        float thrshld{1.0f};
+        switch(device->FmtType)
+        {
+            case DevFmtByte:
+            case DevFmtUByte:
+                thrshld = 127.0f / 128.0f;
+                break;
+            case DevFmtShort:
+            case DevFmtUShort:
+                thrshld = 32767.0f / 32768.0f;
+                break;
+            case DevFmtInt:
+            case DevFmtUInt:
+            case DevFmtFloat:
+                break;
+        }
+        if(device->DitherDepth > 0.0f)
+            thrshld -= 1.0f / device->DitherDepth;
+
+        const float thrshld_dB{std::log10(thrshld) * 20.0f};
+        auto limiter = CreateDeviceLimiter(device, thrshld_dB);
+
+        sample_delay += limiter->getLookAhead();
+        device->Limiter = std::move(limiter);
+        TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB);
+    }
+
+    /* Convert the sample delay from samples to nanosamples to nanoseconds. */
+    device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency;
+    TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()});
+
+    FPUCtl mixer_mode{};
+    for(ALCcontext *context : *device->mContexts.load())
+    {
+        auto GetEffectBuffer = [](ALbuffer *buffer) noexcept -> EffectState::Buffer
+        {
+            if(!buffer) return EffectState::Buffer{};
+            return EffectState::Buffer{buffer, buffer->mData};
+        };
+        std::unique_lock<std::mutex> proplock{context->mPropLock};
+        std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
+
+        /* Clear out unused wet buffers. */
+        auto buffer_not_in_use = [](WetBufferPtr &wetbuffer) noexcept -> bool
+        { return !wetbuffer->mInUse; };
+        auto wetbuffer_iter = std::remove_if(context->mWetBuffers.begin(),
+            context->mWetBuffers.end(), buffer_not_in_use);
+        context->mWetBuffers.erase(wetbuffer_iter, context->mWetBuffers.end());
+
+        if(ALeffectslot *slot{context->mDefaultSlot.get()})
+        {
+            aluInitEffectPanning(&slot->mSlot, context);
+
+            EffectState *state{slot->Effect.State.get()};
+            state->mOutTarget = device->Dry.Buffer;
+            state->deviceUpdate(device, GetEffectBuffer(slot->Buffer));
+            slot->updateProps(context);
+        }
+
+        if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)})
+            std::fill_n(curarray->end(), curarray->size(), nullptr);
+        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);
+
+                aluInitEffectPanning(&slot->mSlot, context);
+
+                EffectState *state{slot->Effect.State.get()};
+                state->mOutTarget = device->Dry.Buffer;
+                state->deviceUpdate(device, GetEffectBuffer(slot->Buffer));
+                slot->updateProps(context);
+            }
+        }
+        slotlock.unlock();
+
+        const uint num_sends{device->NumAuxSends};
+        std::unique_lock<std::mutex> srclock{context->mSourceLock};
+        for(auto &sublist : context->mSourceList)
+        {
+            uint64_t usemask{~sublist.FreeMask};
+            while(usemask)
+            {
+                const int idx{al::countr_zero(usemask)};
+                ALsource *source{sublist.Sources + idx};
+                usemask &= ~(1_u64 << idx);
+
+                auto clear_send = [](ALsource::SendData &send) -> void
+                {
+                    if(send.Slot)
+                        DecrementRef(send.Slot->ref);
+                    send.Slot = nullptr;
+                    send.Gain = 1.0f;
+                    send.GainHF = 1.0f;
+                    send.HFReference = LOWPASSFREQREF;
+                    send.GainLF = 1.0f;
+                    send.LFReference = HIGHPASSFREQREF;
+                };
+                auto send_begin = source->Send.begin() + static_cast<ptrdiff_t>(num_sends);
+                std::for_each(send_begin, source->Send.end(), clear_send);
+
+                source->PropsClean.clear(std::memory_order_release);
+            }
+        }
+
+        /* Clear any pre-existing voice property structs, in case the number of
+         * auxiliary sends is changing. Active sources will have updates
+         * respecified in UpdateAllSourceProps.
+         */
+        VoicePropsItem *vprops{context->mFreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)};
+        while(vprops)
+        {
+            VoicePropsItem *next = vprops->next.load(std::memory_order_relaxed);
+            delete vprops;
+            vprops = next;
+        }
+
+        auto voicelist = context->getVoicesSpan();
+        for(Voice *voice : voicelist)
+        {
+            /* Clear extraneous property set sends. */
+            std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send),
+                VoiceProps::SendData{});
+
+            std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{});
+            for(auto &chandata : voice->mChans)
+            {
+                std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(),
+                    SendParams{});
+            }
+
+            delete voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel);
+
+            /* Force the voice to stopped if it was stopping. */
+            Voice::State vstate{Voice::Stopping};
+            voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped,
+                std::memory_order_acquire, std::memory_order_acquire);
+            if(voice->mSourceID.load(std::memory_order_relaxed) == 0u)
+                continue;
+
+            voice->mStep = 0;
+            voice->mFlags |= VoiceIsFading;
+
+            if(voice->mAmbiOrder && device->mAmbiOrder > voice->mAmbiOrder)
+            {
+                const uint8_t *OrderFromChan{(voice->mFmtChannels == FmtBFormat2D) ?
+                    AmbiIndex::OrderFrom2DChannel().data() :
+                    AmbiIndex::OrderFromChannel().data()};
+
+                const BandSplitter splitter{device->mXOverFreq /
+                    static_cast<float>(device->Frequency)};
+
+                const auto scales = BFormatDec::GetHFOrderScales(voice->mAmbiOrder,
+                    device->mAmbiOrder);
+                for(auto &chandata : voice->mChans)
+                {
+                    chandata.mPrevSamples.fill(0.0f);
+                    chandata.mAmbiScale = scales[*(OrderFromChan++)];
+                    chandata.mAmbiSplitter = splitter;
+                    chandata.mDryParams = DirectParams{};
+                    std::fill_n(chandata.mWetParams.begin(), num_sends, SendParams{});
+                }
+
+                voice->mFlags |= VoiceIsAmbisonic;
+            }
+            else
+            {
+                /* Clear previous samples. */
+                for(auto &chandata : voice->mChans)
+                {
+                    chandata.mPrevSamples.fill(0.0f);
+                    chandata.mDryParams = DirectParams{};
+                    std::fill_n(chandata.mWetParams.begin(), num_sends, SendParams{});
+                }
+
+                voice->mFlags &= ~VoiceIsAmbisonic;
+            }
+
+            if(device->AvgSpeakerDist > 0.0f)
+            {
+                /* Reinitialize the NFC filters for new parameters. */
+                const float w1{SpeedOfSoundMetersPerSec /
+                    (device->AvgSpeakerDist * static_cast<float>(device->Frequency))};
+                for(auto &chandata : voice->mChans)
+                    chandata.mDryParams.NFCtrlFilter.init(w1);
+            }
+        }
+        srclock.unlock();
+
+        context->mPropsClean.test_and_set(std::memory_order_release);
+        UpdateContextProps(context);
+        context->mListener.PropsClean.test_and_set(std::memory_order_release);
+        UpdateListenerProps(context);
+        UpdateAllSourceProps(context);
+    }
+    mixer_mode.leave();
+
+    if(!device->Flags.test(DevicePaused))
+    {
+        try {
+            auto backend = device->Backend.get();
+            backend->start();
+            device->Flags.set(DeviceRunning);
+        }
+        catch(al::backend_exception& e) {
+            device->handleDisconnect("%s", e.what());
+            return ALC_INVALID_DEVICE;
+        }
+    }
+
+    return ALC_NO_ERROR;
+}
+
+
+ALCdevice::ALCdevice(DeviceType type) : Type{type}, mContexts{&EmptyContextArray}
+{
+}
+
+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");
+
+    mHrtf = nullptr;
+
+    auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed);
+    if(oldarray != &EmptyContextArray) delete oldarray;
+}
+
+
+/** Checks if the device handle is valid, and returns a new reference if so. */
+static DeviceRef VerifyDevice(ALCdevice *device)
+{
+    std::lock_guard<std::recursive_mutex> _{ListLock};
+    auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device);
+    if(iter != DeviceList.end() && *iter == device)
+    {
+        (*iter)->add_ref();
+        return DeviceRef{*iter};
+    }
+    return nullptr;
+}
+
+
+ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device) : mDevice{std::move(device)}
+{
+    mPropsClean.test_and_set(std::memory_order_relaxed);
+}
+
+ALCcontext::~ALCcontext()
+{
+    TRACE("Freeing context %p\n", voidp{this});
+
+    size_t count{0};
+    ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)};
+    if(cprops)
+    {
+        ++count;
+        delete cprops;
+    }
+    cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire);
+    while(cprops)
+    {
+        std::unique_ptr<ContextProps> old{cprops};
+        cprops = old->next.load(std::memory_order_relaxed);
+        ++count;
+    }
+    TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s");
+
+    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;
+
+    count = 0;
+    EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)};
+    while(eprops)
+    {
+        std::unique_ptr<EffectSlotProps> old{eprops};
+        eprops = old->next.load(std::memory_order_relaxed);
+        ++count;
+    }
+    TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
+
+    if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)})
+    {
+        al::destroy_n(curarray->end(), curarray->size());
+        delete curarray;
+    }
+    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;
+
+    count = 0;
+    VoicePropsItem *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)};
+    while(vprops)
+    {
+        std::unique_ptr<VoicePropsItem> old{vprops};
+        vprops = old->next.load(std::memory_order_relaxed);
+        ++count;
+    }
+    TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s");
+
+    delete mVoices.exchange(nullptr, std::memory_order_relaxed);
+
+    count = 0;
+    ListenerProps *lprops{mParams.ListenerUpdate.exchange(nullptr, std::memory_order_relaxed)};
+    if(lprops)
+    {
+        ++count;
+        delete lprops;
+    }
+    lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire);
+    while(lprops)
+    {
+        std::unique_ptr<ListenerProps> old{lprops};
+        lprops = old->next.load(std::memory_order_relaxed);
+        ++count;
+    }
+    TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s");
+
+    if(mAsyncEvents)
+    {
+        count = 0;
+        auto evt_vec = mAsyncEvents->getReadVector();
+        if(evt_vec.first.len > 0)
+        {
+            al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), evt_vec.first.len);
+            count += evt_vec.first.len;
+        }
+        if(evt_vec.second.len > 0)
+        {
+            al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.second.buf), evt_vec.second.len);
+            count += evt_vec.second.len;
+        }
+        if(count > 0)
+            TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s");
+        mAsyncEvents->readAdvance(count);
+    }
+}
+
+void ALCcontext::init()
+{
+    if(DefaultEffect.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(1);
+    {
+        VoiceChange *cur{mVoiceChangeTail};
+        while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
+            cur = next;
+        mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
+    }
+
+    mExtensionList = alExtList;
+
+
+    mParams.Matrix = alu::Matrix::Identity();
+    mParams.Velocity = alu::Vector{};
+    mParams.Gain = mListener.Gain;
+    mParams.MetersPerUnit = mListener.mMetersPerUnit;
+    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(LocalContext == this)
+    {
+        WARN("%p released while current on thread\n", voidp{this});
+        ThreadContext.set(nullptr);
+        release();
+    }
+
+    ALCcontext *origctx{this};
+    if(GlobalContext.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<ALCcontext*>;
+        auto alloc_ctx_array = [](const size_t count) -> ContextArray*
+        {
+            if(count == 0) return &EmptyContextArray;
+            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<ALCcontext*>{}, _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 != &EmptyContextArray)
+        {
+            mDevice->waitForMix();
+            delete oldarray;
+        }
+
+        ret = !newarray->empty();
+    }
+    else
+        ret = !oldarray->empty();
+
+    StopEventThrd(this);
+
+    return ret;
+}
+
+
+/**
+ * Checks if the given context is valid, returning a new reference to it if so.
+ */
+static ContextRef VerifyContext(ALCcontext *context)
+{
+    std::lock_guard<std::recursive_mutex> _{ListLock};
+    auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context);
+    if(iter != ContextList.end() && *iter == context)
+    {
+        (*iter)->add_ref();
+        return ContextRef{*iter};
+    }
+    return nullptr;
+}
+
+/** Returns a new reference to the currently active context for this thread. */
+ContextRef GetContextRef(void)
+{
+    ALCcontext *context{LocalContext};
+    if(context)
+        context->add_ref();
+    else
+    {
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        context = GlobalContext.load(std::memory_order_acquire);
+        if(context) context->add_ref();
+    }
+    return ContextRef{context};
+}
+
+
+/************************************************
+ * Standard ALC functions
+ ************************************************/
+
+ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(dev) return dev->LastError.exchange(ALC_NO_ERROR);
+    return LastNullDeviceError.exchange(ALC_NO_ERROR);
+}
+END_API_FUNC
+
+
+ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context)
+START_API_FUNC
+{
+    if(!SuspendDefers)
+        return;
+
+    ContextRef ctx{VerifyContext(context)};
+    if(!ctx)
+        alcSetError(nullptr, ALC_INVALID_CONTEXT);
+    else
+        ctx->deferUpdates();
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context)
+START_API_FUNC
+{
+    if(!SuspendDefers)
+        return;
+
+    ContextRef ctx{VerifyContext(context)};
+    if(!ctx)
+        alcSetError(nullptr, ALC_INVALID_CONTEXT);
+    else
+        ctx->processUpdates();
+}
+END_API_FUNC
+
+
+ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param)
+START_API_FUNC
+{
+    const ALCchar *value{nullptr};
+
+    switch(param)
+    {
+    case ALC_NO_ERROR:
+        value = alcNoError;
+        break;
+
+    case ALC_INVALID_ENUM:
+        value = alcErrInvalidEnum;
+        break;
+
+    case ALC_INVALID_VALUE:
+        value = alcErrInvalidValue;
+        break;
+
+    case ALC_INVALID_DEVICE:
+        value = alcErrInvalidDevice;
+        break;
+
+    case ALC_INVALID_CONTEXT:
+        value = alcErrInvalidContext;
+        break;
+
+    case ALC_OUT_OF_MEMORY:
+        value = alcErrOutOfMemory;
+        break;
+
+    case ALC_DEVICE_SPECIFIER:
+        value = alcDefaultName;
+        break;
+
+    case ALC_ALL_DEVICES_SPECIFIER:
+        if(DeviceRef dev{VerifyDevice(Device)})
+            value = dev->DeviceName.c_str();
+        else
+        {
+            ProbeAllDevicesList();
+            value = alcAllDevicesList.c_str();
+        }
+        break;
+
+    case ALC_CAPTURE_DEVICE_SPECIFIER:
+        if(DeviceRef dev{VerifyDevice(Device)})
+            value = dev->DeviceName.c_str();
+        else
+        {
+            ProbeCaptureDeviceList();
+            value = alcCaptureDeviceList.c_str();
+        }
+        break;
+
+    /* Default devices are always first in the list */
+    case ALC_DEFAULT_DEVICE_SPECIFIER:
+        value = alcDefaultName;
+        break;
+
+    case ALC_DEFAULT_ALL_DEVICES_SPECIFIER:
+        if(alcAllDevicesList.empty())
+            ProbeAllDevicesList();
+
+        /* Copy first entry as default. */
+        alcDefaultAllDevicesSpecifier = alcAllDevicesList.c_str();
+        value = alcDefaultAllDevicesSpecifier.c_str();
+        break;
+
+    case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER:
+        if(alcCaptureDeviceList.empty())
+            ProbeCaptureDeviceList();
+
+        /* Copy first entry as default. */
+        alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.c_str();
+        value = alcCaptureDefaultDeviceSpecifier.c_str();
+        break;
+
+    case ALC_EXTENSIONS:
+        if(VerifyDevice(Device))
+            value = alcExtensionList;
+        else
+            value = alcNoDeviceExtList;
+        break;
+
+    case ALC_HRTF_SPECIFIER_SOFT:
+        if(DeviceRef dev{VerifyDevice(Device)})
+        {
+            std::lock_guard<std::mutex> _{dev->StateLock};
+            value = (dev->mHrtf ? dev->HrtfName.c_str() : "");
+        }
+        else
+            alcSetError(nullptr, ALC_INVALID_DEVICE);
+        break;
+
+    default:
+        alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM);
+        break;
+    }
+
+    return value;
+}
+END_API_FUNC
+
+
+static inline int NumAttrsForDevice(ALCdevice *device)
+{
+    if(device->Type == DeviceType::Capture) return 9;
+    if(device->Type != DeviceType::Loopback) return 29;
+    if(device->FmtChans == DevFmtAmbi3D)
+        return 35;
+    return 29;
+}
+
+static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<int> values)
+{
+    size_t i;
+
+    if(values.empty())
+    {
+        alcSetError(device, ALC_INVALID_VALUE);
+        return 0;
+    }
+
+    if(!device)
+    {
+        switch(param)
+        {
+        case ALC_MAJOR_VERSION:
+            values[0] = alcMajorVersion;
+            return 1;
+        case ALC_MINOR_VERSION:
+            values[0] = alcMinorVersion;
+            return 1;
+
+        case ALC_ATTRIBUTES_SIZE:
+        case ALC_ALL_ATTRIBUTES:
+        case ALC_FREQUENCY:
+        case ALC_REFRESH:
+        case ALC_SYNC:
+        case ALC_MONO_SOURCES:
+        case ALC_STEREO_SOURCES:
+        case ALC_CAPTURE_SAMPLES:
+        case ALC_FORMAT_CHANNELS_SOFT:
+        case ALC_FORMAT_TYPE_SOFT:
+        case ALC_AMBISONIC_LAYOUT_SOFT:
+        case ALC_AMBISONIC_SCALING_SOFT:
+        case ALC_AMBISONIC_ORDER_SOFT:
+        case ALC_MAX_AMBISONIC_ORDER_SOFT:
+            alcSetError(nullptr, ALC_INVALID_DEVICE);
+            return 0;
+
+        default:
+            alcSetError(nullptr, ALC_INVALID_ENUM);
+        }
+        return 0;
+    }
+
+    if(device->Type == DeviceType::Capture)
+    {
+        switch(param)
+        {
+        case ALC_ATTRIBUTES_SIZE:
+            values[0] = NumAttrsForDevice(device);
+            return 1;
+
+        case ALC_ALL_ATTRIBUTES:
+            i = 0;
+            if(values.size() < static_cast<size_t>(NumAttrsForDevice(device)))
+                alcSetError(device, ALC_INVALID_VALUE);
+            else
+            {
+                std::lock_guard<std::mutex> _{device->StateLock};
+                values[i++] = ALC_MAJOR_VERSION;
+                values[i++] = alcMajorVersion;
+                values[i++] = ALC_MINOR_VERSION;
+                values[i++] = alcMinorVersion;
+                values[i++] = ALC_CAPTURE_SAMPLES;
+                values[i++] = static_cast<int>(device->Backend->availableSamples());
+                values[i++] = ALC_CONNECTED;
+                values[i++] = device->Connected.load(std::memory_order_relaxed);
+                values[i++] = 0;
+            }
+            return i;
+
+        case ALC_MAJOR_VERSION:
+            values[0] = alcMajorVersion;
+            return 1;
+        case ALC_MINOR_VERSION:
+            values[0] = alcMinorVersion;
+            return 1;
+
+        case ALC_CAPTURE_SAMPLES:
+            {
+                std::lock_guard<std::mutex> _{device->StateLock};
+                values[0] = static_cast<int>(device->Backend->availableSamples());
+            }
+            return 1;
+
+        case ALC_CONNECTED:
+            {
+                std::lock_guard<std::mutex> _{device->StateLock};
+                values[0] = device->Connected.load(std::memory_order_acquire);
+            }
+            return 1;
+
+        default:
+            alcSetError(device, ALC_INVALID_ENUM);
+        }
+        return 0;
+    }
+
+    /* render device */
+    switch(param)
+    {
+    case ALC_ATTRIBUTES_SIZE:
+        values[0] = NumAttrsForDevice(device);
+        return 1;
+
+    case ALC_ALL_ATTRIBUTES:
+        i = 0;
+        if(values.size() < static_cast<size_t>(NumAttrsForDevice(device)))
+            alcSetError(device, ALC_INVALID_VALUE);
+        else
+        {
+            std::lock_guard<std::mutex> _{device->StateLock};
+            values[i++] = ALC_MAJOR_VERSION;
+            values[i++] = alcMajorVersion;
+            values[i++] = ALC_MINOR_VERSION;
+            values[i++] = alcMinorVersion;
+            values[i++] = ALC_EFX_MAJOR_VERSION;
+            values[i++] = alcEFXMajorVersion;
+            values[i++] = ALC_EFX_MINOR_VERSION;
+            values[i++] = alcEFXMinorVersion;
+
+            values[i++] = ALC_FREQUENCY;
+            values[i++] = static_cast<int>(device->Frequency);
+            if(device->Type != DeviceType::Loopback)
+            {
+                values[i++] = ALC_REFRESH;
+                values[i++] = static_cast<int>(device->Frequency / device->UpdateSize);
+
+                values[i++] = ALC_SYNC;
+                values[i++] = ALC_FALSE;
+            }
+            else
+            {
+                if(device->FmtChans == DevFmtAmbi3D)
+                {
+                    values[i++] = ALC_AMBISONIC_LAYOUT_SOFT;
+                    values[i++] = EnumFromDevAmbi(device->mAmbiLayout);
+
+                    values[i++] = ALC_AMBISONIC_SCALING_SOFT;
+                    values[i++] = EnumFromDevAmbi(device->mAmbiScale);
+
+                    values[i++] = ALC_AMBISONIC_ORDER_SOFT;
+                    values[i++] = static_cast<int>(device->mAmbiOrder);
+                }
+
+                values[i++] = ALC_FORMAT_CHANNELS_SOFT;
+                values[i++] = EnumFromDevFmt(device->FmtChans);
+
+                values[i++] = ALC_FORMAT_TYPE_SOFT;
+                values[i++] = EnumFromDevFmt(device->FmtType);
+            }
+
+            values[i++] = ALC_MONO_SOURCES;
+            values[i++] = static_cast<int>(device->NumMonoSources);
+
+            values[i++] = ALC_STEREO_SOURCES;
+            values[i++] = static_cast<int>(device->NumStereoSources);
+
+            values[i++] = ALC_MAX_AUXILIARY_SENDS;
+            values[i++] = static_cast<int>(device->NumAuxSends);
+
+            values[i++] = ALC_HRTF_SOFT;
+            values[i++] = (device->mHrtf ? ALC_TRUE : ALC_FALSE);
+
+            values[i++] = ALC_HRTF_STATUS_SOFT;
+            values[i++] = device->HrtfStatus;
+
+            values[i++] = ALC_OUTPUT_LIMITER_SOFT;
+            values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE;
+
+            values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT;
+            values[i++] = MaxAmbiOrder;
+
+            values[i++] = 0;
+        }
+        return i;
+
+    case ALC_MAJOR_VERSION:
+        values[0] = alcMajorVersion;
+        return 1;
+
+    case ALC_MINOR_VERSION:
+        values[0] = alcMinorVersion;
+        return 1;
+
+    case ALC_EFX_MAJOR_VERSION:
+        values[0] = alcEFXMajorVersion;
+        return 1;
+
+    case ALC_EFX_MINOR_VERSION:
+        values[0] = alcEFXMinorVersion;
+        return 1;
+
+    case ALC_FREQUENCY:
+        values[0] = static_cast<int>(device->Frequency);
+        return 1;
+
+    case ALC_REFRESH:
+        if(device->Type == DeviceType::Loopback)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        {
+            std::lock_guard<std::mutex> _{device->StateLock};
+            values[0] = static_cast<int>(device->Frequency / device->UpdateSize);
+        }
+        return 1;
+
+    case ALC_SYNC:
+        if(device->Type == DeviceType::Loopback)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = ALC_FALSE;
+        return 1;
+
+    case ALC_FORMAT_CHANNELS_SOFT:
+        if(device->Type != DeviceType::Loopback)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = EnumFromDevFmt(device->FmtChans);
+        return 1;
+
+    case ALC_FORMAT_TYPE_SOFT:
+        if(device->Type != DeviceType::Loopback)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = EnumFromDevFmt(device->FmtType);
+        return 1;
+
+    case ALC_AMBISONIC_LAYOUT_SOFT:
+        if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = EnumFromDevAmbi(device->mAmbiLayout);
+        return 1;
+
+    case ALC_AMBISONIC_SCALING_SOFT:
+        if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = EnumFromDevAmbi(device->mAmbiScale);
+        return 1;
+
+    case ALC_AMBISONIC_ORDER_SOFT:
+        if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D)
+        {
+            alcSetError(device, ALC_INVALID_DEVICE);
+            return 0;
+        }
+        values[0] = static_cast<int>(device->mAmbiOrder);
+        return 1;
+
+    case ALC_MONO_SOURCES:
+        values[0] = static_cast<int>(device->NumMonoSources);
+        return 1;
+
+    case ALC_STEREO_SOURCES:
+        values[0] = static_cast<int>(device->NumStereoSources);
+        return 1;
+
+    case ALC_MAX_AUXILIARY_SENDS:
+        values[0] = static_cast<int>(device->NumAuxSends);
+        return 1;
+
+    case ALC_CONNECTED:
+        {
+            std::lock_guard<std::mutex> _{device->StateLock};
+            values[0] = device->Connected.load(std::memory_order_acquire);
+        }
+        return 1;
+
+    case ALC_HRTF_SOFT:
+        values[0] = (device->mHrtf ? ALC_TRUE : ALC_FALSE);
+        return 1;
+
+    case ALC_HRTF_STATUS_SOFT:
+        values[0] = device->HrtfStatus;
+        return 1;
+
+    case ALC_NUM_HRTF_SPECIFIERS_SOFT:
+        {
+            std::lock_guard<std::mutex> _{device->StateLock};
+            device->HrtfList = EnumerateHrtf(device->DeviceName.c_str());
+            values[0] = static_cast<int>(minz(device->HrtfList.size(),
+                std::numeric_limits<int>::max()));
+        }
+        return 1;
+
+    case ALC_OUTPUT_LIMITER_SOFT:
+        values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE;
+        return 1;
+
+    case ALC_MAX_AMBISONIC_ORDER_SOFT:
+        values[0] = MaxAmbiOrder;
+        return 1;
+
+    default:
+        alcSetError(device, ALC_INVALID_ENUM);
+    }
+    return 0;
+}
+
+ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(size <= 0 || values == nullptr)
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    else
+        GetIntegerv(dev.get(), param, {values, static_cast<uint>(size)});
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(size <= 0 || values == nullptr)
+    {
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+        return;
+    }
+    if(!dev || dev->Type == DeviceType::Capture)
+    {
+        auto ivals = al::vector<int>(static_cast<uint>(size));
+        size_t got{GetIntegerv(dev.get(), pname, ivals)};
+        std::copy_n(ivals.begin(), got, values);
+        return;
+    }
+    /* render device */
+    switch(pname)
+    {
+    case ALC_ATTRIBUTES_SIZE:
+        *values = NumAttrsForDevice(dev.get())+4;
+        break;
+
+    case ALC_ALL_ATTRIBUTES:
+        if(size < NumAttrsForDevice(dev.get())+4)
+            alcSetError(dev.get(), ALC_INVALID_VALUE);
+        else
+        {
+            size_t i{0};
+            std::lock_guard<std::mutex> _{dev->StateLock};
+            values[i++] = ALC_FREQUENCY;
+            values[i++] = dev->Frequency;
+
+            if(dev->Type != DeviceType::Loopback)
+            {
+                values[i++] = ALC_REFRESH;
+                values[i++] = dev->Frequency / dev->UpdateSize;
+
+                values[i++] = ALC_SYNC;
+                values[i++] = ALC_FALSE;
+            }
+            else
+            {
+                if(dev->FmtChans == DevFmtAmbi3D)
+                {
+                    values[i++] = ALC_AMBISONIC_LAYOUT_SOFT;
+                    values[i++] = EnumFromDevAmbi(dev->mAmbiLayout);
+
+                    values[i++] = ALC_AMBISONIC_SCALING_SOFT;
+                    values[i++] = EnumFromDevAmbi(dev->mAmbiScale);
+
+                    values[i++] = ALC_AMBISONIC_ORDER_SOFT;
+                    values[i++] = dev->mAmbiOrder;
+                }
+
+                values[i++] = ALC_FORMAT_CHANNELS_SOFT;
+                values[i++] = EnumFromDevFmt(dev->FmtChans);
+
+                values[i++] = ALC_FORMAT_TYPE_SOFT;
+                values[i++] = EnumFromDevFmt(dev->FmtType);
+            }
+
+            values[i++] = ALC_MONO_SOURCES;
+            values[i++] = dev->NumMonoSources;
+
+            values[i++] = ALC_STEREO_SOURCES;
+            values[i++] = dev->NumStereoSources;
+
+            values[i++] = ALC_MAX_AUXILIARY_SENDS;
+            values[i++] = dev->NumAuxSends;
+
+            values[i++] = ALC_HRTF_SOFT;
+            values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE);
+
+            values[i++] = ALC_HRTF_STATUS_SOFT;
+            values[i++] = dev->HrtfStatus;
+
+            values[i++] = ALC_OUTPUT_LIMITER_SOFT;
+            values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE;
+
+            ClockLatency clock{GetClockLatency(dev.get())};
+            values[i++] = ALC_DEVICE_CLOCK_SOFT;
+            values[i++] = clock.ClockTime.count();
+
+            values[i++] = ALC_DEVICE_LATENCY_SOFT;
+            values[i++] = clock.Latency.count();
+
+            values[i++] = 0;
+        }
+        break;
+
+    case ALC_DEVICE_CLOCK_SOFT:
+        {
+            std::lock_guard<std::mutex> _{dev->StateLock};
+            uint samplecount, refcount;
+            nanoseconds basecount;
+            do {
+                refcount = dev->waitForMix();
+                basecount = dev->ClockBase;
+                samplecount = dev->SamplesDone;
+            } while(refcount != ReadRef(dev->MixCount));
+            basecount += nanoseconds{seconds{samplecount}} / dev->Frequency;
+            *values = basecount.count();
+        }
+        break;
+
+    case ALC_DEVICE_LATENCY_SOFT:
+        {
+            std::lock_guard<std::mutex> _{dev->StateLock};
+            ClockLatency clock{GetClockLatency(dev.get())};
+            *values = clock.Latency.count();
+        }
+        break;
+
+    case ALC_DEVICE_CLOCK_LATENCY_SOFT:
+        if(size < 2)
+            alcSetError(dev.get(), ALC_INVALID_VALUE);
+        else
+        {
+            std::lock_guard<std::mutex> _{dev->StateLock};
+            ClockLatency clock{GetClockLatency(dev.get())};
+            values[0] = clock.ClockTime.count();
+            values[1] = clock.Latency.count();
+        }
+        break;
+
+    default:
+        auto ivals = al::vector<int>(static_cast<uint>(size));
+        size_t got{GetIntegerv(dev.get(), pname, ivals)};
+        std::copy_n(ivals.begin(), got, values);
+        break;
+    }
+}
+END_API_FUNC
+
+
+ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!extName)
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    else
+    {
+        size_t len = strlen(extName);
+        const char *ptr = (dev ? alcExtensionList : alcNoDeviceExtList);
+        while(ptr && *ptr)
+        {
+            if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
+                return ALC_TRUE;
+
+            if((ptr=strchr(ptr, ' ')) != nullptr)
+            {
+                do {
+                    ++ptr;
+                } while(isspace(*ptr));
+            }
+        }
+    }
+    return ALC_FALSE;
+}
+END_API_FUNC
+
+
+ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName)
+START_API_FUNC
+{
+    if(!funcName)
+    {
+        DeviceRef dev{VerifyDevice(device)};
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    }
+    else
+    {
+        for(const auto &func : alcFunctions)
+        {
+            if(strcmp(func.funcName, funcName) == 0)
+                return func.address;
+        }
+    }
+    return nullptr;
+}
+END_API_FUNC
+
+
+ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName)
+START_API_FUNC
+{
+    if(!enumName)
+    {
+        DeviceRef dev{VerifyDevice(device)};
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    }
+    else
+    {
+        for(const auto &enm : alcEnumerations)
+        {
+            if(strcmp(enm.enumName, enumName) == 0)
+                return enm.value;
+        }
+    }
+    return 0;
+}
+END_API_FUNC
+
+
+ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList)
+START_API_FUNC
+{
+    /* Explicitly hold the list lock while taking the StateLock in case the
+     * device is asynchronously destroyed, to ensure this new context is
+     * properly cleaned up after being made.
+     */
+    std::unique_lock<std::recursive_mutex> listlock{ListLock};
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type == DeviceType::Capture || !dev->Connected.load(std::memory_order_relaxed))
+    {
+        listlock.unlock();
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        return nullptr;
+    }
+    std::unique_lock<std::mutex> statelock{dev->StateLock};
+    listlock.unlock();
+
+    dev->LastError.store(ALC_NO_ERROR);
+
+    ALCenum err{UpdateDeviceParams(dev.get(), attrList)};
+    if(err != ALC_NO_ERROR)
+    {
+        alcSetError(dev.get(), err);
+        return nullptr;
+    }
+
+    ContextRef context{new ALCcontext{dev}};
+    context->init();
+
+    if(auto volopt = ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust"))
+    {
+        const float valf{*volopt};
+        if(!std::isfinite(valf))
+            ERR("volume-adjust must be finite: %f\n", valf);
+        else
+        {
+            const float db{clampf(valf, -24.0f, 24.0f)};
+            if(db != valf)
+                WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f);
+            context->mGainBoost = std::pow(10.0f, db/20.0f);
+            TRACE("volume-adjust gain: %f\n", context->mGainBoost);
+        }
+    }
+    UpdateListenerProps(context.get());
+
+    {
+        using ContextArray = al::FlexArray<ALCcontext*>;
+
+        /* Allocate a new context array, which holds 1 more than the current/
+         * old array.
+         */
+        auto *oldarray = device->mContexts.load();
+        const size_t newcount{oldarray->size()+1};
+        std::unique_ptr<ContextArray> newarray{ContextArray::Create(newcount)};
+
+        /* Copy the current/old context handles to the new array, appending the
+         * new context.
+         */
+        auto iter = std::copy(oldarray->begin(), oldarray->end(), newarray->begin());
+        *iter = context.get();
+
+        /* Store the new context array in the device. Wait for any current mix
+         * to finish before deleting the old array.
+         */
+        dev->mContexts.store(newarray.release());
+        if(oldarray != &EmptyContextArray)
+        {
+            dev->waitForMix();
+            delete oldarray;
+        }
+    }
+    statelock.unlock();
+
+    {
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get());
+        ContextList.emplace(iter, context.get());
+    }
+
+    if(ALeffectslot *slot{context->mDefaultSlot.get()})
+    {
+        if(slot->initEffect(&DefaultEffect, context.get()) == AL_NO_ERROR)
+            slot->updateProps(context.get());
+        else
+            ERR("Failed to initialize the default effect\n");
+    }
+
+    TRACE("Created context %p\n", voidp{context.get()});
+    return context.release();
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context)
+START_API_FUNC
+{
+    std::unique_lock<std::recursive_mutex> listlock{ListLock};
+    auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context);
+    if(iter == ContextList.end() || *iter != context)
+    {
+        listlock.unlock();
+        alcSetError(nullptr, ALC_INVALID_CONTEXT);
+        return;
+    }
+    /* Hold a reference to this context so it remains valid until the ListLock
+     * is released.
+     */
+    ContextRef ctx{*iter};
+    ContextList.erase(iter);
+
+    ALCdevice *Device{ctx->mDevice.get()};
+
+    std::lock_guard<std::mutex> _{Device->StateLock};
+    if(!ctx->deinit() && Device->Flags.test(DeviceRunning))
+    {
+        Device->Backend->stop();
+        Device->Flags.reset(DeviceRunning);
+    }
+}
+END_API_FUNC
+
+
+ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void)
+START_API_FUNC
+{
+    ALCcontext *Context{LocalContext};
+    if(!Context) Context = GlobalContext.load();
+    return Context;
+}
+END_API_FUNC
+
+/** Returns the currently active thread-local context. */
+ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void)
+START_API_FUNC
+{ return LocalContext; }
+END_API_FUNC
+
+ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context)
+START_API_FUNC
+{
+    /* context must be valid or nullptr */
+    ContextRef ctx;
+    if(context)
+    {
+        ctx = VerifyContext(context);
+        if(!ctx)
+        {
+            alcSetError(nullptr, ALC_INVALID_CONTEXT);
+            return ALC_FALSE;
+        }
+    }
+    /* Release this reference (if any) to store it in the GlobalContext
+     * pointer. Take ownership of the reference (if any) that was previously
+     * stored there.
+     */
+    ctx = ContextRef{GlobalContext.exchange(ctx.release())};
+
+    /* Reset (decrement) the previous global reference by replacing it with the
+     * thread-local context. Take ownership of the thread-local context
+     * reference (if any), clearing the storage to null.
+     */
+    ctx = ContextRef{LocalContext};
+    if(ctx) ThreadContext.set(nullptr);
+    /* Reset (decrement) the previous thread-local reference. */
+
+    return ALC_TRUE;
+}
+END_API_FUNC
+
+/** Makes the given context the active context for the current thread. */
+ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context)
+START_API_FUNC
+{
+    /* context must be valid or nullptr */
+    ContextRef ctx;
+    if(context)
+    {
+        ctx = VerifyContext(context);
+        if(!ctx)
+        {
+            alcSetError(nullptr, ALC_INVALID_CONTEXT);
+            return ALC_FALSE;
+        }
+    }
+    /* context's reference count is already incremented */
+    ContextRef old{LocalContext};
+    ThreadContext.set(ctx.release());
+
+    return ALC_TRUE;
+}
+END_API_FUNC
+
+
+ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context)
+START_API_FUNC
+{
+    ContextRef ctx{VerifyContext(Context)};
+    if(!ctx)
+    {
+        alcSetError(nullptr, ALC_INVALID_CONTEXT);
+        return nullptr;
+    }
+    return ctx->mDevice.get();
+}
+END_API_FUNC
+
+
+ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName)
+START_API_FUNC
+{
+    DO_INITCONFIG();
+
+    if(!PlaybackFactory)
+    {
+        alcSetError(nullptr, ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    if(deviceName)
+    {
+        if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0
+#ifdef _WIN32
+            /* Some old Windows apps hardcode these expecting OpenAL to use a
+             * specific audio API, even when they're not enumerated. Creative's
+             * router effectively ignores them too.
+             */
+            || al::strcasecmp(deviceName, "DirectSound3D") == 0
+            || al::strcasecmp(deviceName, "DirectSound") == 0
+            || al::strcasecmp(deviceName, "MMSYSTEM") == 0
+#endif
+            || al::strcasecmp(deviceName, "openal-soft") == 0)
+            deviceName = nullptr;
+    }
+
+    DeviceRef device{new ALCdevice{DeviceType::Playback}};
+
+    /* Set output format */
+    device->FmtChans = DevFmtChannelsDefault;
+    device->FmtType = DevFmtTypeDefault;
+    device->Frequency = DEFAULT_OUTPUT_RATE;
+    device->UpdateSize = DEFAULT_UPDATE_SIZE;
+    device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES;
+
+    device->SourcesMax = 256;
+    device->AuxiliaryEffectSlotMax = 64;
+    device->NumAuxSends = DEFAULT_SENDS;
+
+    try {
+        auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback);
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        backend->open(deviceName);
+        device->Backend = std::move(backend);
+    }
+    catch(al::backend_exception &e) {
+        WARN("Failed to open playback device: %s\n", e.what());
+        alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory)
+            ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    deviceName = device->DeviceName.c_str();
+    if(auto chanopt = ConfigValueStr(deviceName, nullptr, "channels"))
+    {
+        static const struct ChannelMap {
+            const char name[16];
+            DevFmtChannels chans;
+            uint order;
+        } chanlist[] = {
+            { "mono",       DevFmtMono,   0 },
+            { "stereo",     DevFmtStereo, 0 },
+            { "quad",       DevFmtQuad,   0 },
+            { "surround51", DevFmtX51,    0 },
+            { "surround61", DevFmtX61,    0 },
+            { "surround71", DevFmtX71,    0 },
+            { "surround51rear", DevFmtX51Rear, 0 },
+            { "ambi1", DevFmtAmbi3D, 1 },
+            { "ambi2", DevFmtAmbi3D, 2 },
+            { "ambi3", DevFmtAmbi3D, 3 },
+        };
+
+        const ALCchar *fmt{chanopt->c_str()};
+        auto iter = std::find_if(std::begin(chanlist), std::end(chanlist),
+            [fmt](const ChannelMap &entry) -> bool
+            { return al::strcasecmp(entry.name, fmt) == 0; }
+        );
+        if(iter == std::end(chanlist))
+            ERR("Unsupported channels: %s\n", fmt);
+        else
+        {
+            device->FmtChans = iter->chans;
+            device->mAmbiOrder = iter->order;
+            device->Flags.set(ChannelsRequest);
+        }
+    }
+    if(auto typeopt = ConfigValueStr(deviceName, nullptr, "sample-type"))
+    {
+        static const struct TypeMap {
+            const char name[16];
+            DevFmtType type;
+        } typelist[] = {
+            { "int8",    DevFmtByte   },
+            { "uint8",   DevFmtUByte  },
+            { "int16",   DevFmtShort  },
+            { "uint16",  DevFmtUShort },
+            { "int32",   DevFmtInt    },
+            { "uint32",  DevFmtUInt   },
+            { "float32", DevFmtFloat  },
+        };
+
+        const ALCchar *fmt{typeopt->c_str()};
+        auto iter = std::find_if(std::begin(typelist), std::end(typelist),
+            [fmt](const TypeMap &entry) -> bool
+            { return al::strcasecmp(entry.name, fmt) == 0; }
+        );
+        if(iter == std::end(typelist))
+            ERR("Unsupported sample-type: %s\n", fmt);
+        else
+        {
+            device->FmtType = iter->type;
+            device->Flags.set(SampleTypeRequest);
+        }
+    }
+
+    if(uint freq{ConfigValueUInt(deviceName, nullptr, "frequency").value_or(0u)})
+    {
+        if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE)
+        {
+            const uint newfreq{clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)};
+            ERR("%uhz request clamped to %uhz\n", freq, newfreq);
+            freq = newfreq;
+        }
+        const double scale{static_cast<double>(freq) / device->Frequency};
+        device->UpdateSize = static_cast<uint>(device->UpdateSize*scale + 0.5);
+        device->BufferSize = static_cast<uint>(device->BufferSize*scale + 0.5);
+        device->Frequency = freq;
+        device->Flags.set(FrequencyRequest);
+    }
+
+    if(auto persizeopt = ConfigValueUInt(deviceName, nullptr, "period_size"))
+        device->UpdateSize = clampu(*persizeopt, 64, 8192);
+
+    if(auto peropt = ConfigValueUInt(deviceName, nullptr, "periods"))
+        device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16);
+    else
+        device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2);
+
+    if(auto srcsmax = ConfigValueUInt(deviceName, nullptr, "sources").value_or(0))
+        device->SourcesMax = srcsmax;
+
+    if(auto slotsmax = ConfigValueUInt(deviceName, nullptr, "slots").value_or(0))
+        device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX);
+
+    if(auto sendsopt = ConfigValueInt(deviceName, nullptr, "sends"))
+        device->NumAuxSends = minu(DEFAULT_SENDS,
+            static_cast<uint>(clampi(*sendsopt, 0, MAX_SENDS)));
+
+    device->NumStereoSources = 1;
+    device->NumMonoSources = device->SourcesMax - device->NumStereoSources;
+
+    if(auto ambiopt = ConfigValueStr(deviceName, nullptr, "ambi-format"))
+    {
+        const ALCchar *fmt{ambiopt->c_str()};
+        if(al::strcasecmp(fmt, "fuma") == 0)
+        {
+            if(device->mAmbiOrder > 3)
+                ERR("FuMa is incompatible with %d%s order ambisonics (up to third-order only)\n",
+                    device->mAmbiOrder,
+                    (((device->mAmbiOrder%100)/10) == 1) ? "th" :
+                    ((device->mAmbiOrder%10) == 1) ? "st" :
+                    ((device->mAmbiOrder%10) == 2) ? "nd" :
+                    ((device->mAmbiOrder%10) == 3) ? "rd" : "th");
+            else
+            {
+                device->mAmbiLayout = DevAmbiLayout::FuMa;
+                device->mAmbiScale = DevAmbiScaling::FuMa;
+            }
+        }
+        else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0)
+        {
+            device->mAmbiLayout = DevAmbiLayout::ACN;
+            device->mAmbiScale = DevAmbiScaling::SN3D;
+        }
+        else if(al::strcasecmp(fmt, "acn+n3d") == 0)
+        {
+            device->mAmbiLayout = DevAmbiLayout::ACN;
+            device->mAmbiScale = DevAmbiScaling::N3D;
+        }
+        else
+            ERR("Unsupported ambi-format: %s\n", fmt);
+    }
+
+    {
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get());
+        DeviceList.emplace(iter, device.get());
+    }
+
+    TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str());
+    return device.release();
+}
+END_API_FUNC
+
+ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device)
+START_API_FUNC
+{
+    std::unique_lock<std::recursive_mutex> listlock{ListLock};
+    auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device);
+    if(iter == DeviceList.end() || *iter != device)
+    {
+        alcSetError(nullptr, ALC_INVALID_DEVICE);
+        return ALC_FALSE;
+    }
+    if((*iter)->Type == DeviceType::Capture)
+    {
+        alcSetError(*iter, ALC_INVALID_DEVICE);
+        return ALC_FALSE;
+    }
+
+    /* Erase the device, and any remaining contexts left on it, from their
+     * respective lists.
+     */
+    DeviceRef dev{*iter};
+    DeviceList.erase(iter);
+
+    std::unique_lock<std::mutex> statelock{dev->StateLock};
+    al::vector<ContextRef> orphanctxs;
+    for(ALCcontext *ctx : *dev->mContexts.load())
+    {
+        auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx);
+        if(ctxiter != ContextList.end() && *ctxiter == ctx)
+        {
+            orphanctxs.emplace_back(ContextRef{*ctxiter});
+            ContextList.erase(ctxiter);
+        }
+    }
+    listlock.unlock();
+
+    for(ContextRef &context : orphanctxs)
+    {
+        WARN("Releasing orphaned context %p\n", voidp{context.get()});
+        context->deinit();
+    }
+    orphanctxs.clear();
+
+    if(dev->Flags.test(DeviceRunning))
+        dev->Backend->stop();
+    dev->Flags.reset(DeviceRunning);
+
+    return ALC_TRUE;
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC capture functions
+ ************************************************/
+ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples)
+START_API_FUNC
+{
+    DO_INITCONFIG();
+
+    if(!CaptureFactory)
+    {
+        alcSetError(nullptr, ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    if(samples <= 0)
+    {
+        alcSetError(nullptr, ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    if(deviceName)
+    {
+        if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0
+            || al::strcasecmp(deviceName, "openal-soft") == 0)
+            deviceName = nullptr;
+    }
+
+    DeviceRef device{new ALCdevice{DeviceType::Capture}};
+
+    auto decompfmt = DecomposeDevFormat(format);
+    if(!decompfmt)
+    {
+        alcSetError(nullptr, ALC_INVALID_ENUM);
+        return nullptr;
+    }
+
+    device->Frequency = frequency;
+    device->FmtChans = decompfmt->chans;
+    device->FmtType = decompfmt->type;
+    device->Flags.set(FrequencyRequest);
+    device->Flags.set(ChannelsRequest);
+    device->Flags.set(SampleTypeRequest);
+
+    device->UpdateSize = static_cast<uint>(samples);
+    device->BufferSize = static_cast<uint>(samples);
+
+    try {
+        TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n",
+            DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
+            device->Frequency, device->UpdateSize, device->BufferSize);
+
+        auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture);
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        backend->open(deviceName);
+        device->Backend = std::move(backend);
+    }
+    catch(al::backend_exception &e) {
+        WARN("Failed to open capture device: %s\n", e.what());
+        alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory)
+            ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    {
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get());
+        DeviceList.emplace(iter, device.get());
+    }
+
+    TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str());
+    return device.release();
+}
+END_API_FUNC
+
+ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device)
+START_API_FUNC
+{
+    std::unique_lock<std::recursive_mutex> listlock{ListLock};
+    auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device);
+    if(iter == DeviceList.end() || *iter != device)
+    {
+        alcSetError(nullptr, ALC_INVALID_DEVICE);
+        return ALC_FALSE;
+    }
+    if((*iter)->Type != DeviceType::Capture)
+    {
+        alcSetError(*iter, ALC_INVALID_DEVICE);
+        return ALC_FALSE;
+    }
+
+    DeviceRef dev{*iter};
+    DeviceList.erase(iter);
+    listlock.unlock();
+
+    std::lock_guard<std::mutex> _{dev->StateLock};
+    if(dev->Flags.test(DeviceRunning))
+        dev->Backend->stop();
+    dev->Flags.reset(DeviceRunning);
+
+    return ALC_TRUE;
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Capture)
+    {
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        return;
+    }
+
+    std::lock_guard<std::mutex> _{dev->StateLock};
+    if(!dev->Connected.load(std::memory_order_acquire))
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else if(!dev->Flags.test(DeviceRunning))
+    {
+        try {
+            auto backend = dev->Backend.get();
+            backend->start();
+            dev->Flags.set(DeviceRunning);
+        }
+        catch(al::backend_exception& e) {
+            dev->handleDisconnect("%s", e.what());
+            alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        }
+    }
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Capture)
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else
+    {
+        std::lock_guard<std::mutex> _{dev->StateLock};
+        if(dev->Flags.test(DeviceRunning))
+            dev->Backend->stop();
+        dev->Flags.reset(DeviceRunning);
+    }
+}
+END_API_FUNC
+
+ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Capture)
+    {
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        return;
+    }
+
+    if(samples < 0 || (samples > 0 && buffer == nullptr))
+    {
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+        return;
+    }
+    if(samples < 1)
+        return;
+
+    std::lock_guard<std::mutex> _{dev->StateLock};
+    BackendBase *backend{dev->Backend.get()};
+
+    const auto usamples = static_cast<uint>(samples);
+    if(usamples > backend->availableSamples())
+    {
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+        return;
+    }
+
+    backend->captureSamples(static_cast<al::byte*>(buffer), usamples);
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC loopback functions
+ ************************************************/
+
+/** Open a loopback device, for manual rendering. */
+ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName)
+START_API_FUNC
+{
+    DO_INITCONFIG();
+
+    /* Make sure the device name, if specified, is us. */
+    if(deviceName && strcmp(deviceName, alcDefaultName) != 0)
+    {
+        alcSetError(nullptr, ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    DeviceRef device{new ALCdevice{DeviceType::Loopback}};
+
+    device->SourcesMax = 256;
+    device->AuxiliaryEffectSlotMax = 64;
+    device->NumAuxSends = DEFAULT_SENDS;
+
+    //Set output format
+    device->BufferSize = 0;
+    device->UpdateSize = 0;
+
+    device->Frequency = DEFAULT_OUTPUT_RATE;
+    device->FmtChans = DevFmtChannelsDefault;
+    device->FmtType = DevFmtTypeDefault;
+
+    if(auto srcsmax = ConfigValueUInt(nullptr, nullptr, "sources").value_or(0))
+        device->SourcesMax = srcsmax;
+
+    if(auto slotsmax = ConfigValueUInt(nullptr, nullptr, "slots").value_or(0))
+        device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX);
+
+    if(auto sendsopt = ConfigValueInt(nullptr, nullptr, "sends"))
+        device->NumAuxSends = minu(DEFAULT_SENDS,
+            static_cast<uint>(clampi(*sendsopt, 0, MAX_SENDS)));
+
+    device->NumStereoSources = 1;
+    device->NumMonoSources = device->SourcesMax - device->NumStereoSources;
+
+    try {
+        auto backend = LoopbackBackendFactory::getFactory().createBackend(device.get(),
+            BackendType::Playback);
+        backend->open("Loopback");
+        device->Backend = std::move(backend);
+    }
+    catch(al::backend_exception &e) {
+        WARN("Failed to open loopback device: %s\n", e.what());
+        alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory)
+            ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE);
+        return nullptr;
+    }
+
+    {
+        std::lock_guard<std::recursive_mutex> _{ListLock};
+        auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get());
+        DeviceList.emplace(iter, device.get());
+    }
+
+    TRACE("Created loopback device %p\n", voidp{device.get()});
+    return device.release();
+}
+END_API_FUNC
+
+/**
+ * Determines if the loopback device supports the given format for rendering.
+ */
+ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Loopback)
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else if(freq <= 0)
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    else
+    {
+        if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value()
+            && freq >= MIN_OUTPUT_RATE && freq <= MAX_OUTPUT_RATE)
+            return ALC_TRUE;
+    }
+
+    return ALC_FALSE;
+}
+END_API_FUNC
+
+/**
+ * Renders some samples into a buffer, using the format last set by the
+ * attributes given to alcCreateContext.
+ */
+FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Loopback)
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else if(samples < 0 || (samples > 0 && buffer == nullptr))
+        alcSetError(dev.get(), ALC_INVALID_VALUE);
+    else
+        dev->renderSamples(buffer, static_cast<uint>(samples), dev->channelsFromFmt());
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC DSP pause/resume functions
+ ************************************************/
+
+/** Pause the DSP to stop audio processing. */
+ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Playback)
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else
+    {
+        std::lock_guard<std::mutex> _{dev->StateLock};
+        if(dev->Flags.test(DeviceRunning))
+            dev->Backend->stop();
+        dev->Flags.reset(DeviceRunning);
+        dev->Flags.set(DevicePaused);
+    }
+}
+END_API_FUNC
+
+/** Resume the DSP to restart audio processing. */
+ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type != DeviceType::Playback)
+    {
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        return;
+    }
+
+    std::lock_guard<std::mutex> _{dev->StateLock};
+    if(!dev->Flags.test(DevicePaused))
+        return;
+    dev->Flags.reset(DevicePaused);
+    if(dev->mContexts.load()->empty())
+        return;
+
+    try {
+        auto backend = dev->Backend.get();
+        backend->start();
+        dev->Flags.set(DeviceRunning);
+    }
+    catch(al::backend_exception& e) {
+        dev->handleDisconnect("%s", e.what());
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    }
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC HRTF functions
+ ************************************************/
+
+/** Gets a string parameter at the given index. */
+ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index)
+START_API_FUNC
+{
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type == DeviceType::Capture)
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+    else switch(paramName)
+    {
+        case ALC_HRTF_SPECIFIER_SOFT:
+            if(index >= 0 && static_cast<uint>(index) < dev->HrtfList.size())
+                return dev->HrtfList[static_cast<uint>(index)].c_str();
+            alcSetError(dev.get(), ALC_INVALID_VALUE);
+            break;
+
+        default:
+            alcSetError(dev.get(), ALC_INVALID_ENUM);
+            break;
+    }
+
+    return nullptr;
+}
+END_API_FUNC
+
+/** Resets the given device output, using the specified attribute list. */
+ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs)
+START_API_FUNC
+{
+    std::unique_lock<std::recursive_mutex> listlock{ListLock};
+    DeviceRef dev{VerifyDevice(device)};
+    if(!dev || dev->Type == DeviceType::Capture)
+    {
+        listlock.unlock();
+        alcSetError(dev.get(), ALC_INVALID_DEVICE);
+        return ALC_FALSE;
+    }
+    std::lock_guard<std::mutex> _{dev->StateLock};
+    listlock.unlock();
+
+    /* Force the backend to stop mixing first since we're resetting. Also reset
+     * the connected state so lost devices can attempt recover.
+     */
+    if(dev->Flags.test(DeviceRunning))
+        dev->Backend->stop();
+    dev->Flags.reset(DeviceRunning);
+    if(!dev->Connected.load(std::memory_order_relaxed))
+    {
+        /* Make sure disconnection is finished before continuing on. */
+        dev->waitForMix();
+
+        for(ALCcontext *ctx : *dev->mContexts.load(std::memory_order_acquire))
+        {
+            /* Clear any pending voice changes and reallocate voices to get a
+             * clean restart.
+             */
+            std::lock_guard<std::mutex> __{ctx->mSourceLock};
+            auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire);
+            while(auto *next = vchg->mNext.load(std::memory_order_acquire))
+                vchg = next;
+            ctx->mCurrentVoiceChange.store(vchg, std::memory_order_release);
+
+            ctx->mVoiceClusters.clear();
+            ctx->allocVoices(std::max<size_t>(256,
+                ctx->mActiveVoiceCount.load(std::memory_order_relaxed)));
+        }
+
+        dev->Connected.store(true);
+    }
+
+    ALCenum err{UpdateDeviceParams(dev.get(), attribs)};
+    if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE;
+
+    alcSetError(dev.get(), err);
+    return ALC_FALSE;
+}
+END_API_FUNC

+ 371 - 0
Engine/lib/openal-soft/Alc/alcmain.h

@@ -0,0 +1,371 @@
+#ifndef ALC_MAIN_H
+#define ALC_MAIN_H
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <bitset>
+#include <chrono>
+#include <cstdint>
+#include <cstddef>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "atomic.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/devformat.h"
+#include "core/filters/splitter.h"
+#include "core/mixer/defs.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "intrusive_ptr.h"
+#include "vector.h"
+
+class BFormatDec;
+struct ALbuffer;
+struct ALeffect;
+struct ALfilter;
+struct BackendBase;
+struct Compressor;
+struct EffectState;
+struct Uhj2Encoder;
+struct bs2b;
+
+using uint = unsigned int;
+
+
+#define MIN_OUTPUT_RATE      8000
+#define MAX_OUTPUT_RATE      192000
+#define DEFAULT_OUTPUT_RATE  44100
+
+#define DEFAULT_UPDATE_SIZE  882 /* 20ms */
+#define DEFAULT_NUM_UPDATES  3
+
+
+enum class DeviceType : unsigned char {
+    Playback,
+    Capture,
+    Loopback
+};
+
+
+enum class RenderMode : unsigned char {
+    Normal,
+    Pairwise,
+    Hrtf
+};
+
+
+struct InputRemixMap {
+    struct TargetMix { Channel channel; float mix; };
+
+    Channel channel;
+    std::array<TargetMix,2> targets;
+};
+
+
+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; }
+};
+
+
+/* Maximum delay in samples for speaker distance compensation. */
+#define MAX_DELAY_LENGTH 1024
+
+struct DistanceComp {
+    struct ChanData {
+        float Gain{1.0f};
+        uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
+        float *Buffer{nullptr};
+    };
+
+    std::array<ChanData,MAX_OUTPUT_CHANNELS> mChannels;
+    al::FlexArray<float,16> mSamples;
+
+    DistanceComp(size_t count) : mSamples{count} { }
+
+    static std::unique_ptr<DistanceComp> Create(size_t numsamples)
+    { return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
+
+    DEF_FAM_NEWDEL(DistanceComp, mSamples)
+};
+
+
+struct BFChannelConfig {
+    float Scale;
+    uint Index;
+};
+
+
+struct MixParams {
+    /* Coefficient channel mapping for mixing to the buffer. */
+    std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
+
+    al::span<FloatBufferLine> Buffer;
+};
+
+struct RealMixParams {
+    al::span<const InputRemixMap> RemixMap;
+    std::array<uint,MaxChannels> ChannelIndex{};
+
+    al::span<FloatBufferLine> Buffer;
+};
+
+enum {
+    // Frequency was requested by the app or config file
+    FrequencyRequest,
+    // Channel configuration was requested by the config file
+    ChannelsRequest,
+    // Sample type was requested by the config file
+    SampleTypeRequest,
+
+    // Specifies if the DSP is paused at user request
+    DevicePaused,
+    // Specifies if the device is currently running
+    DeviceRunning,
+
+    DeviceFlagsCount
+};
+
+struct ALCdevice : public al::intrusive_ref<ALCdevice> {
+    std::atomic<bool> Connected{true};
+    const DeviceType Type{};
+
+    uint Frequency{};
+    uint UpdateSize{};
+    uint BufferSize{};
+
+    DevFmtChannels FmtChans{};
+    DevFmtType FmtType{};
+    bool IsHeadphones{false};
+    uint mAmbiOrder{0};
+    float mXOverFreq{400.0f};
+    /* For DevFmtAmbi* output only, specifies the channel order and
+     * normalization.
+     */
+    DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
+    DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
+
+    std::string DeviceName;
+
+    // Device flags
+    std::bitset<DeviceFlagsCount> Flags{};
+
+    // Maximum number of sources that can be created
+    uint SourcesMax{};
+    // Maximum number of slots that can be created
+    uint AuxiliaryEffectSlotMax{};
+
+    /* Rendering mode. */
+    RenderMode mRenderMode{RenderMode::Normal};
+
+    /* The average speaker distance as determined by the ambdec configuration,
+     * HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
+     */
+    float AvgSpeakerDist{0.0f};
+
+    uint SamplesDone{0u};
+    std::chrono::nanoseconds ClockBase{0};
+    std::chrono::nanoseconds FixedLatency{0};
+
+    /* Temp storage used for mixer processing. */
+    alignas(16) float SourceData[BufferLineSize + MaxResamplerPadding];
+    alignas(16) float ResampledData[BufferLineSize];
+    alignas(16) float FilteredData[BufferLineSize];
+    union {
+        alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
+        alignas(16) float NfcSampleData[BufferLineSize];
+    };
+
+    /* Persistent storage for HRTF mixing. */
+    alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength + HrtfDirectDelay];
+
+    /* Mixing buffer used by the Dry mix and Real output. */
+    al::vector<FloatBufferLine, 16> MixBuffer;
+
+    /* The "dry" path corresponds to the main output. */
+    MixParams Dry;
+    uint NumChannelsPerOrder[MaxAmbiOrder+1]{};
+
+    /* "Real" output, which will be written to the device buffer. May alias the
+     * dry buffer.
+     */
+    RealMixParams RealOut;
+
+    /* HRTF state and info */
+    std::unique_ptr<DirectHrtfState> mHrtfState;
+    al::intrusive_ptr<HrtfStore> mHrtf;
+    uint mIrSize{0};
+
+    /* Ambisonic-to-UHJ encoder */
+    std::unique_ptr<Uhj2Encoder> Uhj_Encoder;
+
+    /* Ambisonic decoder for speakers */
+    std::unique_ptr<BFormatDec> AmbiDecoder;
+
+    /* Stereo-to-binaural filter */
+    std::unique_ptr<bs2b> Bs2b;
+
+    using PostProc = void(ALCdevice::*)(const size_t SamplesToDo);
+    PostProc PostProcess{nullptr};
+
+    std::unique_ptr<Compressor> Limiter;
+
+    /* Delay buffers used to compensate for speaker distances. */
+    std::unique_ptr<DistanceComp> ChannelDelays;
+
+    /* Dithering control. */
+    float DitherDepth{0.0f};
+    uint DitherSeed{0u};
+
+    /* Running count of the mixer invocations, in 31.1 fixed point. This
+     * actually increments *twice* when mixing, first at the start and then at
+     * the end, so the bottom bit indicates if the device is currently mixing
+     * and the upper bits indicates how many mixes have been done.
+     */
+    RefCount MixCount{0u};
+
+    // Contexts created on this device
+    std::atomic<al::FlexArray<ALCcontext*>*> mContexts{nullptr};
+
+    /* 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{};
+    ALCuint NumAuxSends{};
+
+    std::string HrtfName;
+    al::vector<std::string> HrtfList;
+    ALCenum HrtfStatus{ALC_FALSE};
+
+    ALCenum LimiterState{ALC_DONT_CARE_SOFT};
+
+    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;
+
+
+    ALCdevice(DeviceType type);
+    ALCdevice(const ALCdevice&) = delete;
+    ALCdevice& operator=(const ALCdevice&) = delete;
+    ~ALCdevice();
+
+    uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
+    uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
+    uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
+
+    uint waitForMix() const noexcept
+    {
+        uint refcount;
+        while((refcount=MixCount.load(std::memory_order_acquire))&1) {
+        }
+        return refcount;
+    }
+
+    void ProcessHrtf(const size_t SamplesToDo);
+    void ProcessAmbiDec(const size_t SamplesToDo);
+    void ProcessAmbiDecStablized(const size_t SamplesToDo);
+    void ProcessUhj(const size_t SamplesToDo);
+    void ProcessBs2b(const size_t SamplesToDo);
+
+    inline void postProcess(const size_t SamplesToDo)
+    { if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); }
+
+    void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep);
+
+    /* Caller must lock the device state, and the mixer must not be running. */
+    [[gnu::format(printf,2,3)]] void handleDisconnect(const char *msg, ...);
+
+    DEF_NEWDEL(ALCdevice)
+};
+
+/* Must be less than 15 characters (16 including terminating null) for
+ * compatibility with pthread_setname_np limitations. */
+#define MIXER_THREAD_NAME "alsoft-mixer"
+
+#define RECORD_THREAD_NAME "alsoft-record"
+
+
+extern int RTPrioLevel;
+void SetRTPriority(void);
+
+/**
+ * Returns the index for the given channel name (e.g. FrontCenter), or
+ * INVALID_CHANNEL_INDEX if it doesn't exist.
+ */
+inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
+{ return real.ChannelIndex[chan]; }
+#define INVALID_CHANNEL_INDEX ~0u
+
+
+al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
+
+#endif

+ 0 - 675
Engine/lib/openal-soft/Alc/alconfig.c

@@ -1,675 +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
- */
-
-#ifdef _WIN32
-#ifdef __MINGW32__
-#define _WIN32_IE 0x501
-#else
-#define _WIN32_IE 0x400
-#endif
-#endif
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#ifdef _WIN32_IE
-#include <windows.h>
-#include <shlobj.h>
-#endif
-
-#include "alMain.h"
-#include "alconfig.h"
-#include "compat.h"
-#include "bool.h"
-
-
-typedef struct ConfigEntry {
-    char *key;
-    char *value;
-} ConfigEntry;
-
-typedef struct ConfigBlock {
-    ConfigEntry *entries;
-    unsigned int entryCount;
-} ConfigBlock;
-static ConfigBlock cfgBlock;
-
-
-static char *lstrip(char *line)
-{
-    while(isspace(line[0]))
-        line++;
-    return line;
-}
-
-static char *rstrip(char *line)
-{
-    size_t len = strlen(line);
-    while(len > 0 && isspace(line[len-1]))
-        len--;
-    line[len] = 0;
-    return line;
-}
-
-static int readline(FILE *f, char **output, size_t *maxlen)
-{
-    size_t len = 0;
-    int c;
-
-    while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n'))
-        ;
-    if(c == EOF)
-        return 0;
-
-    do {
-        if(len+1 >= *maxlen)
-        {
-            void *temp = NULL;
-            size_t newmax;
-
-            newmax = (*maxlen ? (*maxlen)<<1 : 32);
-            if(newmax > *maxlen)
-                temp = realloc(*output, newmax);
-            if(!temp)
-            {
-                ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen);
-                return 0;
-            }
-
-            *output = temp;
-            *maxlen = newmax;
-        }
-        (*output)[len++] = c;
-        (*output)[len] = '\0';
-    } while((c=fgetc(f)) != EOF && c != '\r' && c != '\n');
-
-    return 1;
-}
-
-
-static char *expdup(const char *str)
-{
-    char *output = NULL;
-    size_t maxlen = 0;
-    size_t len = 0;
-
-    while(*str != '\0')
-    {
-        const char *addstr;
-        size_t addstrlen;
-        size_t i;
-
-        if(str[0] != '$')
-        {
-            const char *next = strchr(str, '$');
-            addstr = str;
-            addstrlen = next ? (size_t)(next-str) : strlen(str);
-
-            str += addstrlen;
-        }
-        else
-        {
-            str++;
-            if(*str == '$')
-            {
-                const char *next = strchr(str+1, '$');
-                addstr = str;
-                addstrlen = next ? (size_t)(next-str) : strlen(str);
-
-                str += addstrlen;
-            }
-            else
-            {
-                bool hasbraces;
-                char envname[1024];
-                size_t k = 0;
-
-                hasbraces = (*str == '{');
-                if(hasbraces) str++;
-
-                while((isalnum(*str) || *str == '_') && k < sizeof(envname)-1)
-                    envname[k++] = *(str++);
-                envname[k++] = '\0';
-
-                if(hasbraces && *str != '}')
-                    continue;
-
-                if(hasbraces) str++;
-                if((addstr=getenv(envname)) == NULL)
-                    continue;
-                addstrlen = strlen(addstr);
-            }
-        }
-        if(addstrlen == 0)
-            continue;
-
-        if(addstrlen >= maxlen-len)
-        {
-            void *temp = NULL;
-            size_t newmax;
-
-            newmax = len+addstrlen+1;
-            if(newmax > maxlen)
-                temp = realloc(output, newmax);
-            if(!temp)
-            {
-                ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, maxlen);
-                return output;
-            }
-
-            output = temp;
-            maxlen = newmax;
-        }
-
-        for(i = 0;i < addstrlen;i++)
-            output[len++] = addstr[i];
-        output[len] = '\0';
-    }
-
-    return output ? output : calloc(1, 1);
-}
-
-
-static void LoadConfigFromFile(FILE *f)
-{
-    char curSection[128] = "";
-    char *buffer = NULL;
-    size_t maxlen = 0;
-    ConfigEntry *ent;
-
-    while(readline(f, &buffer, &maxlen))
-    {
-        char *line, *comment;
-        char key[256] = "";
-        char value[256] = "";
-
-        line = rstrip(lstrip(buffer));
-        if(!line[0]) continue;
-
-        if(line[0] == '[')
-        {
-            char *section = line+1;
-            char *endsection;
-
-            endsection = strchr(section, ']');
-            if(!endsection || section == endsection)
-            {
-                ERR("config parse error: bad line \"%s\"\n", line);
-                continue;
-            }
-            if(endsection[1] != 0)
-            {
-                char *end = endsection+1;
-                while(isspace(*end))
-                    ++end;
-                if(*end != 0 && *end != '#')
-                {
-                    ERR("config parse error: bad line \"%s\"\n", line);
-                    continue;
-                }
-            }
-            *endsection = 0;
-
-            if(strcasecmp(section, "general") == 0)
-                curSection[0] = 0;
-            else
-            {
-                size_t len, p = 0;
-                do {
-                    char *nextp = strchr(section, '%');
-                    if(!nextp)
-                    {
-                        strncpy(curSection+p, section, sizeof(curSection)-1-p);
-                        break;
-                    }
-
-                    len = nextp - section;
-                    if(len > sizeof(curSection)-1-p)
-                        len = sizeof(curSection)-1-p;
-                    strncpy(curSection+p, section, len);
-                    p += len;
-                    section = nextp;
-
-                    if(((section[1] >= '0' && section[1] <= '9') ||
-                        (section[1] >= 'a' && section[1] <= 'f') ||
-                        (section[1] >= 'A' && section[1] <= 'F')) &&
-                       ((section[2] >= '0' && section[2] <= '9') ||
-                        (section[2] >= 'a' && section[2] <= 'f') ||
-                        (section[2] >= 'A' && section[2] <= 'F')))
-                    {
-                        unsigned char b = 0;
-                        if(section[1] >= '0' && section[1] <= '9')
-                            b = (section[1]-'0') << 4;
-                        else if(section[1] >= 'a' && section[1] <= 'f')
-                            b = (section[1]-'a'+0xa) << 4;
-                        else if(section[1] >= 'A' && section[1] <= 'F')
-                            b = (section[1]-'A'+0x0a) << 4;
-                        if(section[2] >= '0' && section[2] <= '9')
-                            b |= (section[2]-'0');
-                        else if(section[2] >= 'a' && section[2] <= 'f')
-                            b |= (section[2]-'a'+0xa);
-                        else if(section[2] >= 'A' && section[2] <= 'F')
-                            b |= (section[2]-'A'+0x0a);
-                        if(p < sizeof(curSection)-1)
-                            curSection[p++] = b;
-                        section += 3;
-                    }
-                    else if(section[1] == '%')
-                    {
-                        if(p < sizeof(curSection)-1)
-                            curSection[p++] = '%';
-                        section += 2;
-                    }
-                    else
-                    {
-                        if(p < sizeof(curSection)-1)
-                            curSection[p++] = '%';
-                        section += 1;
-                    }
-                    if(p < sizeof(curSection)-1)
-                        curSection[p] = 0;
-                } while(p < sizeof(curSection)-1 && *section != 0);
-                curSection[sizeof(curSection)-1] = 0;
-            }
-
-            continue;
-        }
-
-        comment = strchr(line, '#');
-        if(comment) *(comment++) = 0;
-        if(!line[0]) continue;
-
-        if(sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
-           sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
-           sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
-        {
-            /* sscanf doesn't handle '' or "" as empty values, so clip it
-             * manually. */
-            if(strcmp(value, "\"\"") == 0 || strcmp(value, "''") == 0)
-                value[0] = 0;
-        }
-        else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2)
-        {
-            /* Special case for 'key =' */
-            value[0] = 0;
-        }
-        else
-        {
-            ERR("config parse error: malformed option line: \"%s\"\n\n", line);
-            continue;
-        }
-        rstrip(key);
-
-        if(curSection[0] != 0)
-        {
-            size_t len = strlen(curSection);
-            memmove(&key[len+1], key, sizeof(key)-1-len);
-            key[len] = '/';
-            memcpy(key, curSection, len);
-        }
-
-        /* Check if we already have this option set */
-        ent = cfgBlock.entries;
-        while((unsigned int)(ent-cfgBlock.entries) < cfgBlock.entryCount)
-        {
-            if(strcasecmp(ent->key, key) == 0)
-                break;
-            ent++;
-        }
-
-        if((unsigned int)(ent-cfgBlock.entries) >= cfgBlock.entryCount)
-        {
-            /* Allocate a new option entry */
-            ent = realloc(cfgBlock.entries, (cfgBlock.entryCount+1)*sizeof(ConfigEntry));
-            if(!ent)
-            {
-                 ERR("config parse error: error reallocating config entries\n");
-                 continue;
-            }
-            cfgBlock.entries = ent;
-            ent = cfgBlock.entries + cfgBlock.entryCount;
-            cfgBlock.entryCount++;
-
-            ent->key = strdup(key);
-            ent->value = NULL;
-        }
-
-        free(ent->value);
-        ent->value = expdup(value);
-
-        TRACE("found '%s' = '%s'\n", ent->key, ent->value);
-    }
-
-    free(buffer);
-}
-
-#ifdef _WIN32
-void ReadALConfig(void)
-{
-    al_string ppath = AL_STRING_INIT_STATIC();
-    WCHAR buffer[MAX_PATH];
-    const WCHAR *str;
-    FILE *f;
-
-    if(SHGetSpecialFolderPathW(NULL, buffer, CSIDL_APPDATA, FALSE) != FALSE)
-    {
-        al_string filepath = AL_STRING_INIT_STATIC();
-        alstr_copy_wcstr(&filepath, buffer);
-        alstr_append_cstr(&filepath, "\\alsoft.ini");
-
-        TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
-        f = al_fopen(alstr_get_cstr(filepath), "rt");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-        alstr_reset(&filepath);
-    }
-
-    GetProcBinary(&ppath, NULL);
-    if(!alstr_empty(ppath))
-    {
-        alstr_append_cstr(&ppath, "\\alsoft.ini");
-        TRACE("Loading config %s...\n", alstr_get_cstr(ppath));
-        f = al_fopen(alstr_get_cstr(ppath), "r");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-    }
-
-    if((str=_wgetenv(L"ALSOFT_CONF")) != NULL && *str)
-    {
-        al_string filepath = AL_STRING_INIT_STATIC();
-        alstr_copy_wcstr(&filepath, str);
-
-        TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
-        f = al_fopen(alstr_get_cstr(filepath), "rt");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-        alstr_reset(&filepath);
-    }
-
-    alstr_reset(&ppath);
-}
-#else
-void ReadALConfig(void)
-{
-    al_string confpaths = AL_STRING_INIT_STATIC();
-    al_string fname = AL_STRING_INIT_STATIC();
-    const char *str;
-    FILE *f;
-
-    str = "/etc/openal/alsoft.conf";
-
-    TRACE("Loading config %s...\n", str);
-    f = al_fopen(str, "r");
-    if(f)
-    {
-        LoadConfigFromFile(f);
-        fclose(f);
-    }
-
-    if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
-        str = "/etc/xdg";
-    alstr_copy_cstr(&confpaths, str);
-    /* Go through the list in reverse, since "the order of base directories
-     * denotes their importance; the first directory listed is the most
-     * important". Ergo, we need to load the settings from the later dirs
-     * first so that the settings in the earlier dirs override them.
-     */
-    while(!alstr_empty(confpaths))
-    {
-        char *next = strrchr(alstr_get_cstr(confpaths), ':');
-        if(next)
-        {
-            size_t len = next - alstr_get_cstr(confpaths);
-            alstr_copy_cstr(&fname, next+1);
-            VECTOR_RESIZE(confpaths, len, len+1);
-            VECTOR_ELEM(confpaths, len) = 0;
-        }
-        else
-        {
-            alstr_reset(&fname);
-            fname = confpaths;
-            AL_STRING_INIT(confpaths);
-        }
-
-        if(alstr_empty(fname) || VECTOR_FRONT(fname) != '/')
-            WARN("Ignoring XDG config dir: %s\n", alstr_get_cstr(fname));
-        else
-        {
-            if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
-            else alstr_append_cstr(&fname, "alsoft.conf");
-
-            TRACE("Loading config %s...\n", alstr_get_cstr(fname));
-            f = al_fopen(alstr_get_cstr(fname), "r");
-            if(f)
-            {
-                LoadConfigFromFile(f);
-                fclose(f);
-            }
-        }
-        alstr_clear(&fname);
-    }
-
-    if((str=getenv("HOME")) != NULL && *str)
-    {
-        alstr_copy_cstr(&fname, str);
-        if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.alsoftrc");
-        else alstr_append_cstr(&fname, ".alsoftrc");
-
-        TRACE("Loading config %s...\n", alstr_get_cstr(fname));
-        f = al_fopen(alstr_get_cstr(fname), "r");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-    }
-
-    if((str=getenv("XDG_CONFIG_HOME")) != NULL && str[0] != 0)
-    {
-        alstr_copy_cstr(&fname, str);
-        if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
-        else alstr_append_cstr(&fname, "alsoft.conf");
-    }
-    else
-    {
-        alstr_clear(&fname);
-        if((str=getenv("HOME")) != NULL && str[0] != 0)
-        {
-            alstr_copy_cstr(&fname, str);
-            if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.config/alsoft.conf");
-            else alstr_append_cstr(&fname, ".config/alsoft.conf");
-        }
-    }
-    if(!alstr_empty(fname))
-    {
-        TRACE("Loading config %s...\n", alstr_get_cstr(fname));
-        f = al_fopen(alstr_get_cstr(fname), "r");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-    }
-
-    alstr_clear(&fname);
-    GetProcBinary(&fname, NULL);
-    if(!alstr_empty(fname))
-    {
-        if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
-        else alstr_append_cstr(&fname, "alsoft.conf");
-
-        TRACE("Loading config %s...\n", alstr_get_cstr(fname));
-        f = al_fopen(alstr_get_cstr(fname), "r");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-    }
-
-    if((str=getenv("ALSOFT_CONF")) != NULL && *str)
-    {
-        TRACE("Loading config %s...\n", str);
-        f = al_fopen(str, "r");
-        if(f)
-        {
-            LoadConfigFromFile(f);
-            fclose(f);
-        }
-    }
-
-    alstr_reset(&fname);
-    alstr_reset(&confpaths);
-}
-#endif
-
-void FreeALConfig(void)
-{
-    unsigned int i;
-
-    for(i = 0;i < cfgBlock.entryCount;i++)
-    {
-        free(cfgBlock.entries[i].key);
-        free(cfgBlock.entries[i].value);
-    }
-    free(cfgBlock.entries);
-}
-
-const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
-{
-    unsigned int i;
-    char key[256];
-
-    if(!keyName)
-        return def;
-
-    if(blockName && strcasecmp(blockName, "general") != 0)
-    {
-        if(devName)
-            snprintf(key, sizeof(key), "%s/%s/%s", blockName, devName, keyName);
-        else
-            snprintf(key, sizeof(key), "%s/%s", blockName, keyName);
-    }
-    else
-    {
-        if(devName)
-            snprintf(key, sizeof(key), "%s/%s", devName, keyName);
-        else
-        {
-            strncpy(key, keyName, sizeof(key)-1);
-            key[sizeof(key)-1] = 0;
-        }
-    }
-
-    for(i = 0;i < cfgBlock.entryCount;i++)
-    {
-        if(strcmp(cfgBlock.entries[i].key, key) == 0)
-        {
-            TRACE("Found %s = \"%s\"\n", key, cfgBlock.entries[i].value);
-            if(cfgBlock.entries[i].value[0])
-                return cfgBlock.entries[i].value;
-            return def;
-        }
-    }
-
-    if(!devName)
-    {
-        TRACE("Key %s not found\n", key);
-        return def;
-    }
-    return GetConfigValue(NULL, blockName, keyName, def);
-}
-
-int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    return !!val[0];
-}
-
-int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return 0;
-
-    *ret = val;
-    return 1;
-}
-
-int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return 0;
-
-    *ret = strtol(val, NULL, 0);
-    return 1;
-}
-
-int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return 0;
-
-    *ret = strtoul(val, NULL, 0);
-    return 1;
-}
-
-int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return 0;
-
-#ifdef HAVE_STRTOF
-    *ret = strtof(val, NULL);
-#else
-    *ret = (float)strtod(val, NULL);
-#endif
-    return 1;
-}
-
-int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-    if(!val[0]) return 0;
-
-    *ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
-            strcasecmp(val, "on") == 0 || atoi(val) != 0);
-    return 1;
-}
-
-int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
-{
-    const char *val = GetConfigValue(devName, blockName, keyName, "");
-
-    if(!val[0]) return !!def;
-    return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
-            strcasecmp(val, "on") == 0 || atoi(val) != 0);
-}

+ 542 - 0
Engine/lib/openal-soft/Alc/alconfig.cpp

@@ -0,0 +1,542 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2007 by authors.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "alconfig.h"
+
+#include <cstdlib>
+#include <cctype>
+#include <cstring>
+#ifdef _WIN32
+#include <windows.h>
+#include <shlobj.h>
+#endif
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#include <algorithm>
+#include <cstdio>
+#include <string>
+#include <utility>
+
+#include "alfstream.h"
+#include "alstring.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "strutils.h"
+#include "vector.h"
+
+
+namespace {
+
+struct ConfigEntry {
+    std::string key;
+    std::string value;
+};
+al::vector<ConfigEntry> ConfOpts;
+
+
+std::string &lstrip(std::string &line)
+{
+    size_t pos{0};
+    while(pos < line.length() && std::isspace(line[pos]))
+        ++pos;
+    line.erase(0, pos);
+    return line;
+}
+
+bool readline(std::istream &f, std::string &output)
+{
+    while(f.good() && f.peek() == '\n')
+        f.ignore();
+
+    return std::getline(f, output) && !output.empty();
+}
+
+std::string expdup(const char *str)
+{
+    std::string output;
+
+    std::string envval;
+    while(*str != '\0')
+    {
+        const char *addstr;
+        size_t addstrlen;
+
+        if(str[0] != '$')
+        {
+            const char *next = std::strchr(str, '$');
+            addstr = str;
+            addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
+
+            str += addstrlen;
+        }
+        else
+        {
+            str++;
+            if(*str == '$')
+            {
+                const char *next = std::strchr(str+1, '$');
+                addstr = str;
+                addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
+
+                str += addstrlen;
+            }
+            else
+            {
+                const bool hasbraces{(*str == '{')};
+
+                if(hasbraces) str++;
+                const char *envstart = str;
+                while(std::isalnum(*str) || *str == '_')
+                    ++str;
+                if(hasbraces && *str != '}')
+                    continue;
+                const std::string envname{envstart, str};
+                if(hasbraces) str++;
+
+                envval = al::getenv(envname.c_str()).value_or(std::string{});
+                addstr = envval.data();
+                addstrlen = envval.length();
+            }
+        }
+        if(addstrlen == 0)
+            continue;
+
+        output.append(addstr, addstrlen);
+    }
+
+    return output;
+}
+
+void LoadConfigFromFile(std::istream &f)
+{
+    std::string curSection;
+    std::string buffer;
+
+    while(readline(f, buffer))
+    {
+        if(lstrip(buffer).empty())
+            continue;
+
+        if(buffer[0] == '[')
+        {
+            char *line{&buffer[0]};
+            char *section = line+1;
+            char *endsection;
+
+            endsection = std::strchr(section, ']');
+            if(!endsection || section == endsection)
+            {
+                ERR(" config parse error: bad line \"%s\"\n", line);
+                continue;
+            }
+            if(endsection[1] != 0)
+            {
+                char *end = endsection+1;
+                while(std::isspace(*end))
+                    ++end;
+                if(*end != 0 && *end != '#')
+                {
+                    ERR(" config parse error: bad line \"%s\"\n", line);
+                    continue;
+                }
+            }
+            *endsection = 0;
+
+            curSection.clear();
+            if(al::strcasecmp(section, "general") != 0)
+            {
+                do {
+                    char *nextp = std::strchr(section, '%');
+                    if(!nextp)
+                    {
+                        curSection += section;
+                        break;
+                    }
+
+                    curSection.append(section, nextp);
+                    section = nextp;
+
+                    if(((section[1] >= '0' && section[1] <= '9') ||
+                        (section[1] >= 'a' && section[1] <= 'f') ||
+                        (section[1] >= 'A' && section[1] <= 'F')) &&
+                       ((section[2] >= '0' && section[2] <= '9') ||
+                        (section[2] >= 'a' && section[2] <= 'f') ||
+                        (section[2] >= 'A' && section[2] <= 'F')))
+                    {
+                        int b{0};
+                        if(section[1] >= '0' && section[1] <= '9')
+                            b = (section[1]-'0') << 4;
+                        else if(section[1] >= 'a' && section[1] <= 'f')
+                            b = (section[1]-'a'+0xa) << 4;
+                        else if(section[1] >= 'A' && section[1] <= 'F')
+                            b = (section[1]-'A'+0x0a) << 4;
+                        if(section[2] >= '0' && section[2] <= '9')
+                            b |= (section[2]-'0');
+                        else if(section[2] >= 'a' && section[2] <= 'f')
+                            b |= (section[2]-'a'+0xa);
+                        else if(section[2] >= 'A' && section[2] <= 'F')
+                            b |= (section[2]-'A'+0x0a);
+                        curSection += static_cast<char>(b);
+                        section += 3;
+                    }
+                    else if(section[1] == '%')
+                    {
+                        curSection += '%';
+                        section += 2;
+                    }
+                    else
+                    {
+                        curSection += '%';
+                        section += 1;
+                    }
+                } while(*section != 0);
+            }
+
+            continue;
+        }
+
+        auto cmtpos = std::min(buffer.find('#'), buffer.size());
+        while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
+            --cmtpos;
+        if(!cmtpos) continue;
+        buffer.erase(cmtpos);
+
+        auto sep = buffer.find('=');
+        if(sep == std::string::npos)
+        {
+            ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
+            continue;
+        }
+        auto keyend = sep++;
+        while(keyend > 0 && std::isspace(buffer[keyend-1]))
+            --keyend;
+        if(!keyend)
+        {
+            ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
+            continue;
+        }
+        while(sep < buffer.size() && std::isspace(buffer[sep]))
+            sep++;
+
+        std::string fullKey;
+        if(!curSection.empty())
+        {
+            fullKey += curSection;
+            fullKey += '/';
+        }
+        fullKey += buffer.substr(0u, keyend);
+
+        std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
+        if(value.size() > 1)
+        {
+            if((value.front() == '"' && value.back() == '"')
+                || (value.front() == '\'' && value.back() == '\''))
+            {
+                value.pop_back();
+                value.erase(value.begin());
+            }
+        }
+
+        TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
+
+        /* Check if we already have this option set */
+        auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
+        { return entry.key == fullKey; };
+        auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
+        if(ent != ConfOpts.end())
+        {
+            if(!value.empty())
+                ent->value = expdup(value.c_str());
+            else
+                ConfOpts.erase(ent);
+        }
+        else if(!value.empty())
+            ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
+    }
+    ConfOpts.shrink_to_fit();
+}
+
+} // namespace
+
+
+#ifdef _WIN32
+void ReadALConfig()
+{
+    WCHAR buffer[MAX_PATH];
+    if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
+    {
+        std::string filepath{wstr_to_utf8(buffer)};
+        filepath += "\\alsoft.ini";
+
+        TRACE("Loading config %s...\n", filepath.c_str());
+        al::ifstream f{filepath};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+
+    std::string ppath{GetProcBinary().path};
+    if(!ppath.empty())
+    {
+        ppath += "\\alsoft.ini";
+        TRACE("Loading config %s...\n", ppath.c_str());
+        al::ifstream f{ppath};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+
+    if(auto confpath = al::getenv(L"ALSOFT_CONF"))
+    {
+        TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
+        al::ifstream f{*confpath};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+}
+
+#else
+
+void ReadALConfig()
+{
+    const char *str{"/etc/openal/alsoft.conf"};
+
+    TRACE("Loading config %s...\n", str);
+    al::ifstream f{str};
+    if(f.is_open())
+        LoadConfigFromFile(f);
+    f.close();
+
+    std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
+    /* Go through the list in reverse, since "the order of base directories
+     * denotes their importance; the first directory listed is the most
+     * important". Ergo, we need to load the settings from the later dirs
+     * first so that the settings in the earlier dirs override them.
+     */
+    std::string fname;
+    while(!confpaths.empty())
+    {
+        auto next = confpaths.find_last_of(':');
+        if(next < confpaths.length())
+        {
+            fname = confpaths.substr(next+1);
+            confpaths.erase(next);
+        }
+        else
+        {
+            fname = confpaths;
+            confpaths.clear();
+        }
+
+        if(fname.empty() || fname.front() != '/')
+            WARN("Ignoring XDG config dir: %s\n", fname.c_str());
+        else
+        {
+            if(fname.back() != '/') fname += "/alsoft.conf";
+            else fname += "alsoft.conf";
+
+            TRACE("Loading config %s...\n", fname.c_str());
+            f = al::ifstream{fname};
+            if(f.is_open())
+                LoadConfigFromFile(f);
+        }
+        fname.clear();
+    }
+
+#ifdef __APPLE__
+    CFBundleRef mainBundle = CFBundleGetMainBundle();
+    if(mainBundle)
+    {
+        unsigned char fileName[PATH_MAX];
+        CFURLRef configURL;
+
+        if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
+           CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
+        {
+            f = al::ifstream{reinterpret_cast<char*>(fileName)};
+            if(f.is_open())
+                LoadConfigFromFile(f);
+        }
+    }
+#endif
+
+    if(auto homedir = al::getenv("HOME"))
+    {
+        fname = *homedir;
+        if(fname.back() != '/') fname += "/.alsoftrc";
+        else fname += ".alsoftrc";
+
+        TRACE("Loading config %s...\n", fname.c_str());
+        f = al::ifstream{fname};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+
+    if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
+    {
+        fname = *configdir;
+        if(fname.back() != '/') fname += "/alsoft.conf";
+        else fname += "alsoft.conf";
+    }
+    else
+    {
+        fname.clear();
+        if(auto homedir = al::getenv("HOME"))
+        {
+            fname = *homedir;
+            if(fname.back() != '/') fname += "/.config/alsoft.conf";
+            else fname += ".config/alsoft.conf";
+        }
+    }
+    if(!fname.empty())
+    {
+        TRACE("Loading config %s...\n", fname.c_str());
+        f = al::ifstream{fname};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+
+    std::string ppath{GetProcBinary().path};
+    if(!ppath.empty())
+    {
+        if(ppath.back() != '/') ppath += "/alsoft.conf";
+        else ppath += "alsoft.conf";
+
+        TRACE("Loading config %s...\n", ppath.c_str());
+        f = al::ifstream{ppath};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+
+    if(auto confname = al::getenv("ALSOFT_CONF"))
+    {
+        TRACE("Loading config %s...\n", confname->c_str());
+        f = al::ifstream{*confname};
+        if(f.is_open())
+            LoadConfigFromFile(f);
+    }
+}
+#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);
+}
+
+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)));
+}
+
+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)));
+}
+
+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));
+}
+
+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);
+}
+
+int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int 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);
+}

+ 10 - 7
Engine/lib/openal-soft/Alc/alconfig.h

@@ -1,17 +1,20 @@
 #ifndef ALCONFIG_H
 #define ALCONFIG_H
 
-void ReadALConfig(void);
-void FreeALConfig(void);
+#include <string>
+
+#include "aloptional.h"
+
+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);
 
-int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret);
-int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret);
-int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret);
-int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret);
-int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret);
+al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
+al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
+al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
+al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
+al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
 
 #endif /* ALCONFIG_H */

+ 282 - 0
Engine/lib/openal-soft/Alc/alcontext.h

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

+ 0 - 58
Engine/lib/openal-soft/Alc/alstring.h

@@ -1,58 +0,0 @@
-#ifndef ALSTRING_H
-#define ALSTRING_H
-
-#include <string.h>
-
-#include "vector.h"
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef char al_string_char_type;
-TYPEDEF_VECTOR(al_string_char_type, al_string)
-TYPEDEF_VECTOR(al_string, vector_al_string)
-
-inline void alstr_reset(al_string *str)
-{ VECTOR_DEINIT(*str); }
-#define AL_STRING_INIT(_x)       do { (_x) = (al_string)NULL; } while(0)
-#define AL_STRING_INIT_STATIC()  ((al_string)NULL)
-#define AL_STRING_DEINIT(_x)     alstr_reset(&(_x))
-
-inline size_t alstr_length(const_al_string str)
-{ return VECTOR_SIZE(str); }
-
-inline ALboolean alstr_empty(const_al_string str)
-{ return alstr_length(str) == 0; }
-
-inline const al_string_char_type *alstr_get_cstr(const_al_string str)
-{ return str ? &VECTOR_FRONT(str) : ""; }
-
-void alstr_clear(al_string *str);
-
-int alstr_cmp(const_al_string str1, const_al_string str2);
-int alstr_cmp_cstr(const_al_string str1, const al_string_char_type *str2);
-
-void alstr_copy(al_string *str, const_al_string from);
-void alstr_copy_cstr(al_string *str, const al_string_char_type *from);
-void alstr_copy_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to);
-
-void alstr_append_char(al_string *str, const al_string_char_type c);
-void alstr_append_cstr(al_string *str, const al_string_char_type *from);
-void alstr_append_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to);
-
-#ifdef _WIN32
-#include <wchar.h>
-/* Windows-only methods to deal with WideChar strings. */
-void alstr_copy_wcstr(al_string *str, const wchar_t *from);
-void alstr_append_wcstr(al_string *str, const wchar_t *from);
-void alstr_copy_wrange(al_string *str, const wchar_t *from, const wchar_t *to);
-void alstr_append_wrange(al_string *str, const wchar_t *from, const wchar_t *to);
-#endif
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* ALSTRING_H */

+ 2018 - 0
Engine/lib/openal-soft/Alc/alu.cpp

@@ -0,0 +1,2018 @@
+/**
+ * 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 "alu.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <climits>
+#include <cmath>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <new>
+#include <numeric>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "async_event.h"
+#include "atomic.h"
+#include "bformatdec.h"
+#include "core/ambidefs.h"
+#include "core/bs2b.h"
+#include "core/bsinc_tables.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/fpu_ctrl.h"
+#include "core/mastering.h"
+#include "core/mixer/defs.h"
+#include "core/uhjfilter.h"
+#include "effects/base.h"
+#include "effectslot.h"
+#include "front_stablizer.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+#include "vecmat.h"
+#include "voice.h"
+#include "voice_change.h"
+
+struct CTag;
+#ifdef HAVE_SSE
+struct SSETag;
+#endif
+#ifdef HAVE_SSE2
+struct SSE2Tag;
+#endif
+#ifdef HAVE_SSE4_1
+struct SSE4Tag;
+#endif
+#ifdef HAVE_NEON
+struct NEONTag;
+#endif
+struct CopyTag;
+struct PointTag;
+struct LerpTag;
+struct CubicTag;
+struct BSincTag;
+struct FastBSincTag;
+
+
+static_assert(MaxResamplerPadding >= BSincPointsMax, "MaxResamplerPadding is too small");
+static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two");
+
+
+namespace {
+
+constexpr uint MaxPitch{10};
+
+static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
+static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
+    "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
+
+using namespace std::placeholders;
+
+float InitConeScale()
+{
+    float ret{1.0f};
+    if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES"))
+    {
+        if(al::strcasecmp(optval->c_str(), "true") == 0
+            || strtol(optval->c_str(), nullptr, 0) == 1)
+            ret *= 0.5f;
+    }
+    return ret;
+}
+
+float InitZScale()
+{
+    float ret{1.0f};
+    if(auto optval = al::getenv("__ALSOFT_REVERSE_Z"))
+    {
+        if(al::strcasecmp(optval->c_str(), "true") == 0
+            || strtol(optval->c_str(), nullptr, 0) == 1)
+            ret *= -1.0f;
+    }
+    return ret;
+}
+
+} // namespace
+
+/* Cone scalar */
+const float ConeScale{InitConeScale()};
+
+/* Localized Z scalar for mono sources */
+const float ZScale{InitZScale()};
+
+namespace {
+
+struct ChanMap {
+    Channel channel;
+    float angle;
+    float elevation;
+};
+
+using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+    const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
+    float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize);
+
+HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
+
+inline HrtfDirectMixerFunc SelectHrtfMixer(void)
+{
+#ifdef HAVE_NEON
+    if((CPUCapFlags&CPU_CAP_NEON))
+        return MixDirectHrtf_<NEONTag>;
+#endif
+#ifdef HAVE_SSE
+    if((CPUCapFlags&CPU_CAP_SSE))
+        return MixDirectHrtf_<SSETag>;
+#endif
+
+    return MixDirectHrtf_<CTag>;
+}
+
+
+inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table)
+{
+    size_t si{BSincScaleCount - 1};
+    float sf{0.0f};
+
+    if(increment > MixerFracOne)
+    {
+        sf = MixerFracOne / static_cast<float>(increment);
+        sf = maxf(0.0f, (BSincScaleCount-1) * (sf-table->scaleBase) * table->scaleRange);
+        si = float2uint(sf);
+        /* The interpolation factor is fit to this diagonally-symmetric curve
+         * to reduce the transition ripple caused by interpolating different
+         * scales of the sinc function.
+         */
+        sf = 1.0f - std::cos(std::asin(sf - static_cast<float>(si)));
+    }
+
+    state->sf = sf;
+    state->m = table->m[si];
+    state->l = (state->m/2) - 1;
+    state->filter = table->Tab + table->filterOffset[si];
+}
+
+inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
+{
+    switch(resampler)
+    {
+    case Resampler::Point:
+        return Resample_<PointTag,CTag>;
+    case Resampler::Linear:
+#ifdef HAVE_NEON
+        if((CPUCapFlags&CPU_CAP_NEON))
+            return Resample_<LerpTag,NEONTag>;
+#endif
+#ifdef HAVE_SSE4_1
+        if((CPUCapFlags&CPU_CAP_SSE4_1))
+            return Resample_<LerpTag,SSE4Tag>;
+#endif
+#ifdef HAVE_SSE2
+        if((CPUCapFlags&CPU_CAP_SSE2))
+            return Resample_<LerpTag,SSE2Tag>;
+#endif
+        return Resample_<LerpTag,CTag>;
+    case Resampler::Cubic:
+        return Resample_<CubicTag,CTag>;
+    case Resampler::BSinc12:
+    case Resampler::BSinc24:
+        if(increment <= MixerFracOne)
+        {
+            /* fall-through */
+        case Resampler::FastBSinc12:
+        case Resampler::FastBSinc24:
+#ifdef HAVE_NEON
+            if((CPUCapFlags&CPU_CAP_NEON))
+                return Resample_<FastBSincTag,NEONTag>;
+#endif
+#ifdef HAVE_SSE
+            if((CPUCapFlags&CPU_CAP_SSE))
+                return Resample_<FastBSincTag,SSETag>;
+#endif
+            return Resample_<FastBSincTag,CTag>;
+        }
+#ifdef HAVE_NEON
+        if((CPUCapFlags&CPU_CAP_NEON))
+            return Resample_<BSincTag,NEONTag>;
+#endif
+#ifdef HAVE_SSE
+        if((CPUCapFlags&CPU_CAP_SSE))
+            return Resample_<BSincTag,SSETag>;
+#endif
+        return Resample_<BSincTag,CTag>;
+    }
+
+    return Resample_<PointTag,CTag>;
+}
+
+} // namespace
+
+void aluInit(void)
+{
+    MixDirectHrtf = SelectHrtfMixer();
+}
+
+
+ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state)
+{
+    switch(resampler)
+    {
+    case Resampler::Point:
+    case Resampler::Linear:
+    case Resampler::Cubic:
+        break;
+    case Resampler::FastBSinc12:
+    case Resampler::BSinc12:
+        BsincPrepare(increment, &state->bsinc, &bsinc12);
+        break;
+    case Resampler::FastBSinc24:
+    case Resampler::BSinc24:
+        BsincPrepare(increment, &state->bsinc, &bsinc24);
+        break;
+    }
+    return SelectResampler(resampler, increment);
+}
+
+
+void ALCdevice::ProcessHrtf(const size_t SamplesToDo)
+{
+    /* HRTF is stereo output only. */
+    const uint lidx{RealOut.ChannelIndex[FrontLeft]};
+    const uint ridx{RealOut.ChannelIndex[FrontRight]};
+
+    MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
+        mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo);
+}
+
+void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
+{
+    AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
+}
+
+void ALCdevice::ProcessAmbiDecStablized(const size_t SamplesToDo)
+{
+    /* Decode with front image stablization. */
+    const uint lidx{RealOut.ChannelIndex[FrontLeft]};
+    const uint ridx{RealOut.ChannelIndex[FrontRight]};
+    const uint cidx{RealOut.ChannelIndex[FrontCenter]};
+
+    AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx,
+        SamplesToDo);
+}
+
+void ALCdevice::ProcessUhj(const size_t SamplesToDo)
+{
+    /* UHJ is stereo output only. */
+    const uint lidx{RealOut.ChannelIndex[FrontLeft]};
+    const uint ridx{RealOut.ChannelIndex[FrontRight]};
+
+    /* Encode to stereo-compatible 2-channel UHJ output. */
+    Uhj_Encoder->encode(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer.data(),
+        SamplesToDo);
+}
+
+void ALCdevice::ProcessBs2b(const size_t SamplesToDo)
+{
+    /* First, decode the ambisonic mix to the "real" output. */
+    AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
+
+    /* BS2B is stereo output only. */
+    const uint lidx{RealOut.ChannelIndex[FrontLeft]};
+    const uint ridx{RealOut.ChannelIndex[FrontRight]};
+
+    /* Now apply the BS2B binaural/crossfeed filter. */
+    bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(),
+        SamplesToDo);
+}
+
+
+namespace {
+
+/* This RNG method was created based on the math found in opusdec. It's quick,
+ * and starting with a seed value of 22222, is suitable for generating
+ * whitenoise.
+ */
+inline uint dither_rng(uint *seed) noexcept
+{
+    *seed = (*seed * 96314165) + 907633515;
+    return *seed;
+}
+
+
+inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
+{
+    if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa();
+    if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D();
+    return AmbiScale::FromN3D();
+}
+
+inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
+{
+    if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
+    return AmbiIndex::FromACN();
+}
+
+inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
+{
+    if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
+    return AmbiIndex::FromACN2D();
+}
+
+
+bool CalcContextParams(ALCcontext *ctx)
+{
+    ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)};
+    if(!props) return false;
+
+    ctx->mParams.DopplerFactor = props->DopplerFactor;
+    ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
+
+    ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
+    ctx->mParams.mDistanceModel = props->mDistanceModel;
+
+    AtomicReplaceHead(ctx->mFreeContextProps, props);
+    return true;
+}
+
+bool CalcListenerParams(ALCcontext *ctx)
+{
+    ListenerProps *props{ctx->mParams.ListenerUpdate.exchange(nullptr,
+        std::memory_order_acq_rel)};
+    if(!props) return false;
+
+    /* AT then UP */
+    alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
+    N.normalize();
+    alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
+    V.normalize();
+    /* Build and normalize right-vector */
+    alu::Vector U{N.cross_product(V)};
+    U.normalize();
+
+    const alu::MatrixR<double> rot{
+        U[0], V[0], -N[0], 0.0,
+        U[1], V[1], -N[1], 0.0,
+        U[2], V[2], -N[2], 0.0,
+         0.0,  0.0,   0.0, 1.0};
+    const alu::VectorR<double> pos{props->Position[0],props->Position[1],props->Position[2],1.0};
+    const alu::VectorR<double> vel{props->Velocity[0],props->Velocity[1],props->Velocity[2],0.0};
+    const alu::Vector P{alu::cast_to<float>(rot * pos)};
+
+    ctx->mParams.Matrix = alu::Matrix{
+         U[0],  V[0], -N[0], 0.0f,
+         U[1],  V[1], -N[1], 0.0f,
+         U[2],  V[2], -N[2], 0.0f,
+        -P[0], -P[1], -P[2], 1.0f};
+    ctx->mParams.Velocity = alu::cast_to<float>(rot * vel);
+
+    ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
+    ctx->mParams.MetersPerUnit = props->MetersPerUnit;
+
+    AtomicReplaceHead(ctx->mFreeListenerProps, props);
+    return true;
+}
+
+bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ALCcontext *context)
+{
+    EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)};
+    if(!props) return false;
+
+    /* If the effect slot target changed, clear the first sorted entry to force
+     * a re-sort.
+     */
+    if(slot->Target != props->Target)
+        *sorted_slots = nullptr;
+    slot->Gain = props->Gain;
+    slot->AuxSendAuto = props->AuxSendAuto;
+    slot->Target = props->Target;
+    slot->EffectType = props->Type;
+    slot->mEffectProps = props->Props;
+    if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb)
+    {
+        slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor;
+        slot->DecayTime = props->Props.Reverb.DecayTime;
+        slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio;
+        slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio;
+        slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit;
+        slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF;
+    }
+    else
+    {
+        slot->RoomRolloff = 0.0f;
+        slot->DecayTime = 0.0f;
+        slot->DecayLFRatio = 0.0f;
+        slot->DecayHFRatio = 0.0f;
+        slot->DecayHFLimit = false;
+        slot->AirAbsorptionGainHF = 1.0f;
+    }
+
+    EffectState *state{props->State.release()};
+    EffectState *oldstate{slot->mEffectState};
+    slot->mEffectState = state;
+
+    /* Only release the old state if it won't get deleted, since we can't be
+     * deleting/freeing anything in the mixer.
+     */
+    if(!oldstate->releaseIfNoDelete())
+    {
+        /* Otherwise, if it would be deleted send it off with a release event. */
+        RingBuffer *ring{context->mAsyncEvents.get()};
+        auto evt_vec = ring->getWriteVector();
+        if LIKELY(evt_vec.first.len > 0)
+        {
+            AsyncEvent *evt{::new(evt_vec.first.buf) AsyncEvent{EventType_ReleaseEffectState}};
+            evt->u.mEffectState = oldstate;
+            ring->writeAdvance(1);
+        }
+        else
+        {
+            /* If writing the event failed, the queue was probably full. Store
+             * the old state in the property object where it can eventually be
+             * cleaned up sometime later (not ideal, but better than blocking
+             * or leaking).
+             */
+            props->State.reset(oldstate);
+        }
+    }
+
+    AtomicReplaceHead(context->mFreeEffectslotProps, props);
+
+    EffectTarget output;
+    if(EffectSlot *target{slot->Target})
+        output = EffectTarget{&target->Wet, nullptr};
+    else
+    {
+        ALCdevice *device{context->mDevice.get()};
+        output = EffectTarget{&device->Dry, &device->RealOut};
+    }
+    state->update(context, slot, &slot->mEffectProps, output);
+    return true;
+}
+
+
+/* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
+ * front.
+ */
+inline float ScaleAzimuthFront(float azimuth, float scale)
+{
+    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);
+    return azimuth;
+}
+
+/* Wraps the given value in radians to stay between [-pi,+pi] */
+inline float WrapRadians(float r)
+{
+    constexpr float Pi{al::MathDefs<float>::Pi()};
+    constexpr float Pi2{al::MathDefs<float>::Tau()};
+    if(r >  Pi) return std::fmod(Pi+r, Pi2) - Pi;
+    if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2);
+    return r;
+}
+
+/* Begin ambisonic rotation helpers.
+ *
+ * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation
+ * matrix. Higher orders, however, are more complicated. The method implemented
+ * here is a recursive algorithm (the rotation for first-order is used to help
+ * generate the second-order rotation, which helps generate the third-order
+ * rotation, etc).
+ *
+ * Adapted from
+ * <https://github.com/polarch/Spherical-Harmonic-Transform/blob/master/getSHrotMtx.m>,
+ * provided under the BSD 3-Clause license.
+ *
+ * Copyright (c) 2015, Archontis Politis
+ * Copyright (c) 2019, Christopher Robinson
+ *
+ * The u, v, and w coefficients used for generating higher-order rotations are
+ * precomputed since they're constant. The second-order coefficients are
+ * followed by the third-order coefficients, etc.
+ */
+struct RotatorCoeffs {
+    float u, v, w;
+
+    template<size_t N0, size_t N1>
+    static std::array<RotatorCoeffs,N0+N1> ConcatArrays(const std::array<RotatorCoeffs,N0> &lhs,
+        const std::array<RotatorCoeffs,N1> &rhs)
+    {
+        std::array<RotatorCoeffs,N0+N1> ret;
+        auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin());
+        std::copy(rhs.cbegin(), rhs.cend(), iter);
+        return ret;
+    }
+
+    template<int l, int num_elems=l*2+1>
+    static std::array<RotatorCoeffs,num_elems*num_elems> GenCoeffs()
+    {
+        std::array<RotatorCoeffs,num_elems*num_elems> ret{};
+        auto coeffs = ret.begin();
+
+        for(int m{-l};m <= l;++m)
+        {
+            for(int n{-l};n <= l;++n)
+            {
+                // compute u,v,w terms of Eq.8.1 (Table I)
+                const bool d{m == 0}; // the delta function d_m0
+                const float denom{static_cast<float>((std::abs(n) == l) ?
+                    (2*l) * (2*l - 1) : (l*l - n*n))};
+
+                const int abs_m{std::abs(m)};
+                coeffs->u = std::sqrt(static_cast<float>(l*l - m*m)/denom);
+                coeffs->v = std::sqrt(static_cast<float>(l+abs_m-1) * static_cast<float>(l+abs_m) /
+                    denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f;
+                coeffs->w = std::sqrt(static_cast<float>(l-abs_m-1) * static_cast<float>(l-abs_m) /
+                    denom) * (1.0f-d) * -0.5f;
+                ++coeffs;
+            }
+        }
+
+        return ret;
+    }
+};
+const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(),
+    RotatorCoeffs::GenCoeffs<3>());
+
+/**
+ * Given the matrix, pre-filled with the (zeroth- and) first-order rotation
+ * coefficients, this fills in the coefficients for the higher orders up to and
+ * including the given order. The matrix is in ACN layout.
+ */
+void AmbiRotator(std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &matrix,
+    const int order)
+{
+    /* Don't do anything for < 2nd order. */
+    if(order < 2) return;
+
+    auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
+        const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
+    {
+        const float ri1{ R[static_cast<uint>(i+2)][ 1+2]};
+        const float rim1{R[static_cast<uint>(i+2)][-1+2]};
+        const float ri0{ R[static_cast<uint>(i+2)][ 0+2]};
+
+        auto vec = R[static_cast<uint>(a+l-1) + last_band].cbegin() + last_band;
+        if(n == -l)
+            return ri1*vec[0] + rim1*vec[static_cast<uint>(l-1)*size_t{2}];
+        if(n == l)
+            return ri1*vec[static_cast<uint>(l-1)*size_t{2}] - rim1*vec[0];
+        return ri0*vec[static_cast<uint>(n+l-1)];
+    };
+
+    auto U = [P](const int l, const int m, const int n, const size_t last_band,
+        const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
+    {
+        return P(0, l, m, n, last_band, R);
+    };
+    auto V = [P](const int l, const int m, const int n, const size_t last_band,
+        const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
+    {
+        if(m > 0)
+        {
+            const bool d{m == 1};
+            const float p0{P( 1, l,  m-1, n, last_band, R)};
+            const float p1{P(-1, l, -m+1, n, last_band, R)};
+            return d ? p0*std::sqrt(2.0f) : (p0 - p1);
+        }
+        const bool d{m == -1};
+        const float p0{P( 1, l,  m+1, n, last_band, R)};
+        const float p1{P(-1, l, -m-1, n, last_band, R)};
+        return d ? p1*std::sqrt(2.0f) : (p0 + p1);
+    };
+    auto W = [P](const int l, const int m, const int n, const size_t last_band,
+        const std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> &R)
+    {
+        assert(m != 0);
+        if(m > 0)
+        {
+            const float p0{P( 1, l,  m+1, n, last_band, R)};
+            const float p1{P(-1, l, -m-1, n, last_band, R)};
+            return p0 + p1;
+        }
+        const float p0{P( 1, l,  m-1, n, last_band, R)};
+        const float p1{P(-1, l, -m+1, n, last_band, R)};
+        return p0 - p1;
+    };
+
+    // compute rotation matrix of each subsequent band recursively
+    auto coeffs = RotatorCoeffArray.cbegin();
+    size_t band_idx{4}, last_band{1};
+    for(int l{2};l <= order;++l)
+    {
+        size_t y{band_idx};
+        for(int m{-l};m <= l;++m,++y)
+        {
+            size_t x{band_idx};
+            for(int n{-l};n <= l;++n,++x)
+            {
+                float r{0.0f};
+
+                // computes Eq.8.1
+                const float u{coeffs->u};
+                if(u != 0.0f) r += u * U(l, m, n, last_band, matrix);
+                const float v{coeffs->v};
+                if(v != 0.0f) r += v * V(l, m, n, last_band, matrix);
+                const float w{coeffs->w};
+                if(w != 0.0f) r += w * W(l, m, n, last_band, matrix);
+
+                matrix[y][x] = r;
+                ++coeffs;
+            }
+        }
+        last_band = band_idx;
+        band_idx += static_cast<uint>(l)*size_t{2} + 1;
+    }
+}
+/* End ambisonic rotation helpers. */
+
+
+struct GainTriplet { float Base, HF, LF; };
+
+void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos,
+    const float Distance, const float Spread, const GainTriplet &DryGain,
+    const al::span<const GainTriplet,MAX_SENDS> WetGain, EffectSlot *(&SendSlots)[MAX_SENDS],
+    const VoiceProps *props, const ContextParams &Context, const ALCdevice *Device)
+{
+    static const ChanMap MonoMap[1]{
+        { FrontCenter, 0.0f, 0.0f }
+    }, RearMap[2]{
+        { BackLeft,  Deg2Rad(-150.0f), Deg2Rad(0.0f) },
+        { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }
+    }, QuadMap[4]{
+        { FrontLeft,  Deg2Rad( -45.0f), Deg2Rad(0.0f) },
+        { FrontRight, Deg2Rad(  45.0f), Deg2Rad(0.0f) },
+        { BackLeft,   Deg2Rad(-135.0f), Deg2Rad(0.0f) },
+        { BackRight,  Deg2Rad( 135.0f), Deg2Rad(0.0f) }
+    }, X51Map[6]{
+        { FrontLeft,   Deg2Rad( -30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad(  30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(   0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { SideLeft,    Deg2Rad(-110.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad( 110.0f), Deg2Rad(0.0f) }
+    }, X61Map[7]{
+        { FrontLeft,   Deg2Rad(-30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad( 30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(  0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { BackCenter,  Deg2Rad(180.0f), Deg2Rad(0.0f) },
+        { SideLeft,    Deg2Rad(-90.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad( 90.0f), Deg2Rad(0.0f) }
+    }, X71Map[8]{
+        { FrontLeft,   Deg2Rad( -30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad(  30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(   0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { BackLeft,    Deg2Rad(-150.0f), Deg2Rad(0.0f) },
+        { BackRight,   Deg2Rad( 150.0f), Deg2Rad(0.0f) },
+        { SideLeft,    Deg2Rad( -90.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad(  90.0f), Deg2Rad(0.0f) }
+    };
+
+    ChanMap StereoMap[2]{
+        { FrontLeft,  Deg2Rad(-30.0f), Deg2Rad(0.0f) },
+        { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }
+    };
+
+    const auto Frequency = static_cast<float>(Device->Frequency);
+    const uint NumSends{Device->NumAuxSends};
+
+    const size_t num_channels{voice->mChans.size()};
+    ASSUME(num_channels > 0);
+
+    for(auto &chandata : voice->mChans)
+    {
+        chandata.mDryParams.Hrtf.Target = HrtfFilter{};
+        chandata.mDryParams.Gains.Target.fill(0.0f);
+        std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends,
+            [](SendParams &params) -> void { params.Gains.Target.fill(0.0f); });
+    }
+
+    DirectMode DirectChannels{props->DirectChannels};
+    const ChanMap *chans{nullptr};
+    float downmix_gain{1.0f};
+    switch(voice->mFmtChannels)
+    {
+    case FmtMono:
+        chans = MonoMap;
+        /* Mono buffers are never played direct. */
+        DirectChannels = DirectMode::Off;
+        break;
+
+    case FmtStereo:
+        if(DirectChannels == DirectMode::Off)
+        {
+            /* Convert counter-clockwise to clock-wise, and wrap between
+             * [-pi,+pi].
+             */
+            StereoMap[0].angle = WrapRadians(-props->StereoPan[0]);
+            StereoMap[1].angle = WrapRadians(-props->StereoPan[1]);
+        }
+
+        chans = StereoMap;
+        downmix_gain = 1.0f / 2.0f;
+        break;
+
+    case FmtRear:
+        chans = RearMap;
+        downmix_gain = 1.0f / 2.0f;
+        break;
+
+    case FmtQuad:
+        chans = QuadMap;
+        downmix_gain = 1.0f / 4.0f;
+        break;
+
+    case FmtX51:
+        chans = X51Map;
+        /* NOTE: Excludes LFE. */
+        downmix_gain = 1.0f / 5.0f;
+        break;
+
+    case FmtX61:
+        chans = X61Map;
+        /* NOTE: Excludes LFE. */
+        downmix_gain = 1.0f / 6.0f;
+        break;
+
+    case FmtX71:
+        chans = X71Map;
+        /* NOTE: Excludes LFE. */
+        downmix_gain = 1.0f / 7.0f;
+        break;
+
+    case FmtBFormat2D:
+    case FmtBFormat3D:
+        DirectChannels = DirectMode::Off;
+        break;
+    }
+
+    voice->mFlags &= ~(VoiceHasHrtf | VoiceHasNfc);
+    if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
+    {
+        /* Special handling for B-Format sources. */
+
+        if(Device->AvgSpeakerDist > 0.0f)
+        {
+            if(!(Distance > std::numeric_limits<float>::epsilon()))
+            {
+                /* NOTE: The NFCtrlFilters were created with a w0 of 0, which
+                 * is what we want for FOA input. The first channel may have
+                 * been previously re-adjusted if panned, so reset it.
+                 */
+                voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
+            }
+            else
+            {
+                /* Clamp the distance for really close sources, to prevent
+                 * excessive bass.
+                 */
+                const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
+                const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
+
+                /* Only need to adjust the first channel of a B-Format source. */
+                voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
+            }
+
+            voice->mFlags |= VoiceHasNfc;
+        }
+
+        /* Panning a B-Format sound toward some direction is easy. Just pan the
+         * first (W) channel as a normal mono sound. The angular spread is used
+         * as a directional scalar to blend between full coverage and full
+         * panning.
+         */
+        const float coverage{!(Distance > std::numeric_limits<float>::epsilon()) ? 1.0f :
+            (Spread * (1.0f/al::MathDefs<float>::Tau()))};
+
+        auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode)
+        {
+            if(mode != RenderMode::Pairwise)
+                return CalcDirectionCoeffs({xpos, ypos, zpos}, 0.0f);
+
+            /* Clamp Y, in case rounding errors caused it to end up outside
+             * of -1...+1.
+             */
+            const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+            /* Negate Z for right-handed coords with -Z in front. */
+            const float az{std::atan2(xpos, -zpos)};
+
+            /* A scalar of 1.5 for plain stereo results in +/-60 degrees
+             * being moved to +/-90 degrees for direct right and left
+             * speaker responses.
+             */
+            return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f);
+        };
+        auto coeffs = calc_coeffs(Device->mRenderMode);
+        std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1,
+            std::bind(std::multiplies<float>{}, _1, 1.0f-coverage));
+
+        /* NOTE: W needs to be scaled according to channel scaling. */
+        auto&& scales = GetAmbiScales(voice->mAmbiScaling);
+        ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0],
+            voice->mChans[0].mDryParams.Gains.Target);
+        for(uint i{0};i < NumSends;i++)
+        {
+            if(const EffectSlot *Slot{SendSlots[i]})
+                ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0],
+                    voice->mChans[0].mWetParams[i].Gains.Target);
+        }
+
+        if(coverage > 0.0f)
+        {
+            /* Local B-Format sources have their XYZ channels rotated according
+             * to the orientation.
+             */
+            /* AT then UP */
+            alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f};
+            N.normalize();
+            alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f};
+            V.normalize();
+            if(!props->HeadRelative)
+            {
+                N = Context.Matrix * N;
+                V = Context.Matrix * V;
+            }
+            /* Build and normalize right-vector */
+            alu::Vector U{N.cross_product(V)};
+            U.normalize();
+
+            /* Build a rotation matrix. Manually fill the zeroth- and first-
+             * order elements, then construct the rotation for the higher
+             * orders.
+             */
+            std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> shrot{};
+            shrot[0][0] = 1.0f;
+            shrot[1][1] =  U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0];
+            shrot[2][1] = -U[1]; shrot[2][2] =  V[1]; shrot[2][3] =  N[1];
+            shrot[3][1] =  U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2];
+            AmbiRotator(shrot, static_cast<int>(minu(voice->mAmbiOrder, Device->mAmbiOrder)));
+
+            /* Convert the rotation matrix for input ordering and scaling, and
+             * whether input is 2D or 3D.
+             */
+            const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ?
+                GetAmbi2DLayout(voice->mAmbiLayout).data() :
+                GetAmbiLayout(voice->mAmbiLayout).data()};
+
+            static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,};
+            static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,};
+            for(size_t c{1};c < num_channels;c++)
+            {
+                const size_t acn{index_map[c]};
+                const size_t order{AmbiIndex::OrderFromChannel()[acn]};
+                const size_t tocopy{ChansPerOrder[order]};
+                const size_t offset{OrderOffset[order]};
+                const float scale{scales[acn] * coverage};
+                auto in = shrot.cbegin() + offset;
+
+                coeffs = std::array<float,MaxAmbiChannels>{};
+                for(size_t x{0};x < tocopy;++x)
+                    coeffs[offset+x] = in[x][acn] * scale;
+
+                ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
+                    voice->mChans[c].mDryParams.Gains.Target);
+
+                for(uint i{0};i < NumSends;i++)
+                {
+                    if(const EffectSlot *Slot{SendSlots[i]})
+                        ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
+                            voice->mChans[c].mWetParams[i].Gains.Target);
+                }
+            }
+        }
+    }
+    else if(DirectChannels != DirectMode::Off && Device->FmtChans != DevFmtAmbi3D)
+    {
+        /* Direct source channels always play local. Skip the virtual channels
+         * and write inputs to the matching real outputs.
+         */
+        voice->mDirect.Buffer = Device->RealOut.Buffer;
+
+        for(size_t c{0};c < num_channels;c++)
+        {
+            uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+            if(idx != INVALID_CHANNEL_INDEX)
+                voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
+            else if(DirectChannels == DirectMode::RemixMismatch)
+            {
+                auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool
+                { return chans[c].channel == map.channel; };
+                auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(),
+                    Device->RealOut.RemixMap.cend(), match_channel);
+                if(remap != Device->RealOut.RemixMap.cend())
+                    for(const auto &target : remap->targets)
+                    {
+                        idx = GetChannelIdxByName(Device->RealOut, target.channel);
+                        if(idx != INVALID_CHANNEL_INDEX)
+                            voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base *
+                                target.mix;
+                    }
+            }
+        }
+
+        /* Auxiliary sends still use normal channel panning since they mix to
+         * B-Format, which can't channel-match.
+         */
+        for(size_t c{0};c < num_channels;c++)
+        {
+            const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f);
+
+            for(uint i{0};i < NumSends;i++)
+            {
+                if(const EffectSlot *Slot{SendSlots[i]})
+                    ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
+                        voice->mChans[c].mWetParams[i].Gains.Target);
+            }
+        }
+    }
+    else if(Device->mRenderMode == RenderMode::Hrtf)
+    {
+        /* Full HRTF rendering. Skip the virtual channels and render to the
+         * real outputs.
+         */
+        voice->mDirect.Buffer = Device->RealOut.Buffer;
+
+        if(Distance > std::numeric_limits<float>::epsilon())
+        {
+            const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+            const float az{std::atan2(xpos, -zpos)};
+
+            /* Get the HRIR coefficients and delays just once, for the given
+             * source direction.
+             */
+            GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread,
+                voice->mChans[0].mDryParams.Hrtf.Target.Coeffs,
+                voice->mChans[0].mDryParams.Hrtf.Target.Delay);
+            voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base * downmix_gain;
+
+            /* Remaining channels use the same results as the first. */
+            for(size_t c{1};c < num_channels;c++)
+            {
+                /* Skip LFE */
+                if(chans[c].channel == LFE) continue;
+                voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target;
+            }
+
+            /* Calculate the directional coefficients once, which apply to all
+             * input channels of the source sends.
+             */
+            const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
+
+            for(size_t c{0};c < num_channels;c++)
+            {
+                /* Skip LFE */
+                if(chans[c].channel == LFE)
+                    continue;
+                for(uint i{0};i < NumSends;i++)
+                {
+                    if(const EffectSlot *Slot{SendSlots[i]})
+                        ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
+                            voice->mChans[c].mWetParams[i].Gains.Target);
+                }
+            }
+        }
+        else
+        {
+            /* Local sources on HRTF play with each channel panned to its
+             * relative location around the listener, providing "virtual
+             * speaker" responses.
+             */
+            for(size_t c{0};c < num_channels;c++)
+            {
+                /* Skip LFE */
+                if(chans[c].channel == LFE)
+                    continue;
+
+                /* Get the HRIR coefficients and delays for this channel
+                 * position.
+                 */
+                GetHrtfCoeffs(Device->mHrtf.get(), chans[c].elevation, chans[c].angle,
+                    std::numeric_limits<float>::infinity(), Spread,
+                    voice->mChans[c].mDryParams.Hrtf.Target.Coeffs,
+                    voice->mChans[c].mDryParams.Hrtf.Target.Delay);
+                voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base;
+
+                /* Normal panning for auxiliary sends. */
+                const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread);
+
+                for(uint i{0};i < NumSends;i++)
+                {
+                    if(const EffectSlot *Slot{SendSlots[i]})
+                        ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
+                            voice->mChans[c].mWetParams[i].Gains.Target);
+                }
+            }
+        }
+
+        voice->mFlags |= VoiceHasHrtf;
+    }
+    else
+    {
+        /* Non-HRTF rendering. Use normal panning to the output. */
+
+        if(Distance > std::numeric_limits<float>::epsilon())
+        {
+            /* Calculate NFC filter coefficient if needed. */
+            if(Device->AvgSpeakerDist > 0.0f)
+            {
+                /* Clamp the distance for really close sources, to prevent
+                 * excessive bass.
+                 */
+                const float mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
+                const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)};
+
+                /* Adjust NFC filters. */
+                for(size_t c{0};c < num_channels;c++)
+                    voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
+
+                voice->mFlags |= VoiceHasNfc;
+            }
+
+            /* Calculate the directional coefficients once, which apply to all
+             * input channels.
+             */
+            auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode)
+            {
+                if(mode != RenderMode::Pairwise)
+                    return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread);
+                const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+                const float az{std::atan2(xpos, -zpos)};
+                return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread);
+            };
+            const auto coeffs = calc_coeffs(Device->mRenderMode);
+
+            for(size_t c{0};c < num_channels;c++)
+            {
+                /* Special-case LFE */
+                if(chans[c].channel == LFE)
+                {
+                    if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
+                    {
+                        const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+                        if(idx != INVALID_CHANNEL_INDEX)
+                            voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
+                    }
+                    continue;
+                }
+
+                ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base * downmix_gain,
+                    voice->mChans[c].mDryParams.Gains.Target);
+                for(uint i{0};i < NumSends;i++)
+                {
+                    if(const EffectSlot *Slot{SendSlots[i]})
+                        ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base * downmix_gain,
+                            voice->mChans[c].mWetParams[i].Gains.Target);
+                }
+            }
+        }
+        else
+        {
+            if(Device->AvgSpeakerDist > 0.0f)
+            {
+                /* If the source distance is 0, simulate a plane-wave by using
+                 * infinite distance, which results in a w0 of 0.
+                 */
+                constexpr float w0{0.0f};
+                for(size_t c{0};c < num_channels;c++)
+                    voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
+
+                voice->mFlags |= VoiceHasNfc;
+            }
+
+            for(size_t c{0};c < num_channels;c++)
+            {
+                /* Special-case LFE */
+                if(chans[c].channel == LFE)
+                {
+                    if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
+                    {
+                        const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+                        if(idx != INVALID_CHANNEL_INDEX)
+                            voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base;
+                    }
+                    continue;
+                }
+
+                const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise)
+                    ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle,
+                    chans[c].elevation, Spread);
+
+                ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base,
+                    voice->mChans[c].mDryParams.Gains.Target);
+                for(uint i{0};i < NumSends;i++)
+                {
+                    if(const EffectSlot *Slot{SendSlots[i]})
+                        ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base,
+                            voice->mChans[c].mWetParams[i].Gains.Target);
+                }
+            }
+        }
+    }
+
+    {
+        const float hfNorm{props->Direct.HFReference / Frequency};
+        const float lfNorm{props->Direct.LFReference / Frequency};
+
+        voice->mDirect.FilterType = AF_None;
+        if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
+        if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
+
+        auto &lowpass = voice->mChans[0].mDryParams.LowPass;
+        auto &highpass = voice->mChans[0].mDryParams.HighPass;
+        lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f);
+        highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f);
+        for(size_t c{1};c < num_channels;c++)
+        {
+            voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
+            voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
+        }
+    }
+    for(uint i{0};i < NumSends;i++)
+    {
+        const float hfNorm{props->Send[i].HFReference / Frequency};
+        const float lfNorm{props->Send[i].LFReference / Frequency};
+
+        voice->mSend[i].FilterType = AF_None;
+        if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
+        if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass;
+
+        auto &lowpass = voice->mChans[0].mWetParams[i].LowPass;
+        auto &highpass = voice->mChans[0].mWetParams[i].HighPass;
+        lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f);
+        highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f);
+        for(size_t c{1};c < num_channels;c++)
+        {
+            voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
+            voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
+        }
+    }
+}
+
+void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context)
+{
+    const ALCdevice *Device{context->mDevice.get()};
+    EffectSlot *SendSlots[MAX_SENDS];
+
+    voice->mDirect.Buffer = Device->Dry.Buffer;
+    for(uint i{0};i < Device->NumAuxSends;i++)
+    {
+        SendSlots[i] = props->Send[i].Slot;
+        if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
+        {
+            SendSlots[i] = nullptr;
+            voice->mSend[i].Buffer = {};
+        }
+        else
+            voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
+    }
+
+    /* Calculate the stepping value */
+    const auto Pitch = static_cast<float>(voice->mFrequency) /
+        static_cast<float>(Device->Frequency) * props->Pitch;
+    if(Pitch > float{MaxPitch})
+        voice->mStep = MaxPitch<<MixerFracBits;
+    else
+        voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
+    voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
+
+    /* Calculate gains */
+    GainTriplet DryGain;
+    DryGain.Base  = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain *
+        context->mParams.Gain, GainMixMax);
+    DryGain.HF = props->Direct.GainHF;
+    DryGain.LF = props->Direct.GainLF;
+    GainTriplet WetGain[MAX_SENDS];
+    for(uint i{0};i < Device->NumAuxSends;i++)
+    {
+        WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) *
+            props->Send[i].Gain * context->mParams.Gain, GainMixMax);
+        WetGain[i].HF = props->Send[i].GainHF;
+        WetGain[i].LF = props->Send[i].GainLF;
+    }
+
+    CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props,
+        context->mParams, Device);
+}
+
+void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ALCcontext *context)
+{
+    const ALCdevice *Device{context->mDevice.get()};
+    const uint NumSends{Device->NumAuxSends};
+
+    /* Set mixing buffers and get send parameters. */
+    voice->mDirect.Buffer = Device->Dry.Buffer;
+    EffectSlot *SendSlots[MAX_SENDS];
+    float RoomRolloff[MAX_SENDS];
+    GainTriplet DecayDistance[MAX_SENDS];
+    for(uint i{0};i < NumSends;i++)
+    {
+        SendSlots[i] = props->Send[i].Slot;
+        if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
+        {
+            SendSlots[i] = nullptr;
+            RoomRolloff[i] = 0.0f;
+            DecayDistance[i].Base = 0.0f;
+            DecayDistance[i].LF = 0.0f;
+            DecayDistance[i].HF = 0.0f;
+        }
+        else if(SendSlots[i]->AuxSendAuto)
+        {
+            RoomRolloff[i] = SendSlots[i]->RoomRolloff + props->RoomRolloffFactor;
+            /* Calculate the distances to where this effect's decay reaches
+             * -60dB.
+             */
+            DecayDistance[i].Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec;
+            DecayDistance[i].LF = DecayDistance[i].Base * SendSlots[i]->DecayLFRatio;
+            DecayDistance[i].HF = DecayDistance[i].Base * SendSlots[i]->DecayHFRatio;
+            if(SendSlots[i]->DecayHFLimit)
+            {
+                const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF};
+                if(airAbsorption < 1.0f)
+                {
+                    /* Calculate the distance to where this effect's air
+                     * absorption reaches -60dB, and limit the effect's HF
+                     * decay distance (so it doesn't take any longer to decay
+                     * than the air would allow).
+                     */
+                    constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/};
+                    const float absorb_dist{log10_decaygain / std::log10(airAbsorption)};
+                    DecayDistance[i].HF = minf(absorb_dist, DecayDistance[i].HF);
+                }
+            }
+        }
+        else
+        {
+            /* If the slot's auxiliary send auto is off, the data sent to the
+             * effect slot is the same as the dry path, sans filter effects */
+            RoomRolloff[i] = props->RolloffFactor;
+            DecayDistance[i].Base = 0.0f;
+            DecayDistance[i].LF = 0.0f;
+            DecayDistance[i].HF = 0.0f;
+        }
+
+        if(!SendSlots[i])
+            voice->mSend[i].Buffer = {};
+        else
+            voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
+    }
+
+    /* Transform source to listener space (convert to head relative) */
+    alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f};
+    alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
+    alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f};
+    if(!props->HeadRelative)
+    {
+        /* Transform source vectors */
+        Position = context->mParams.Matrix * Position;
+        Velocity = context->mParams.Matrix * Velocity;
+        Direction = context->mParams.Matrix * Direction;
+    }
+    else
+    {
+        /* Offset the source velocity to be relative of the listener velocity */
+        Velocity += context->mParams.Velocity;
+    }
+
+    const bool directional{Direction.normalize() > 0.0f};
+    alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
+    const float Distance{ToSource.normalize()};
+
+    /* Initial source gain */
+    GainTriplet DryGain{props->Gain, 1.0f, 1.0f};
+    GainTriplet WetGain[MAX_SENDS];
+    for(uint i{0};i < NumSends;i++)
+        WetGain[i] = DryGain;
+
+    /* Calculate distance attenuation */
+    float ClampedDist{Distance};
+
+    switch(context->mParams.SourceDistanceModel ? props->mDistanceModel
+        : context->mParams.mDistanceModel)
+    {
+        case DistanceModel::InverseClamped:
+            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
+            if(props->MaxDistance < props->RefDistance) break;
+            /*fall-through*/
+        case DistanceModel::Inverse:
+            if(!(props->RefDistance > 0.0f))
+                ClampedDist = props->RefDistance;
+            else
+            {
+                float dist{lerp(props->RefDistance, ClampedDist, props->RolloffFactor)};
+                if(dist > 0.0f) DryGain.Base *= props->RefDistance / dist;
+                for(uint i{0};i < NumSends;i++)
+                {
+                    dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
+                    if(dist > 0.0f) WetGain[i].Base *= props->RefDistance / dist;
+                }
+            }
+            break;
+
+        case DistanceModel::LinearClamped:
+            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
+            if(props->MaxDistance < props->RefDistance) break;
+            /*fall-through*/
+        case DistanceModel::Linear:
+            if(!(props->MaxDistance != props->RefDistance))
+                ClampedDist = props->RefDistance;
+            else
+            {
+                float attn{props->RolloffFactor * (ClampedDist-props->RefDistance) /
+                    (props->MaxDistance-props->RefDistance)};
+                DryGain.Base *= maxf(1.0f - attn, 0.0f);
+                for(uint i{0};i < NumSends;i++)
+                {
+                    attn = RoomRolloff[i] * (ClampedDist-props->RefDistance) /
+                        (props->MaxDistance-props->RefDistance);
+                    WetGain[i].Base *= maxf(1.0f - attn, 0.0f);
+                }
+            }
+            break;
+
+        case DistanceModel::ExponentClamped:
+            ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance);
+            if(props->MaxDistance < props->RefDistance) break;
+            /*fall-through*/
+        case DistanceModel::Exponent:
+            if(!(ClampedDist > 0.0f && props->RefDistance > 0.0f))
+                ClampedDist = props->RefDistance;
+            else
+            {
+                const float dist_ratio{ClampedDist/props->RefDistance};
+                DryGain.Base *= std::pow(dist_ratio, -props->RolloffFactor);
+                for(uint i{0};i < NumSends;i++)
+                    WetGain[i].Base *= std::pow(dist_ratio, -RoomRolloff[i]);
+            }
+            break;
+
+        case DistanceModel::Disable:
+            ClampedDist = props->RefDistance;
+            break;
+    }
+
+    /* Calculate directional soundcones */
+    if(directional && props->InnerAngle < 360.0f)
+    {
+        const float Angle{Rad2Deg(std::acos(Direction.dot_product(ToSource)) * ConeScale * -2.0f)};
+
+        float ConeGain, ConeHF;
+        if(!(Angle > props->InnerAngle))
+        {
+            ConeGain = 1.0f;
+            ConeHF = 1.0f;
+        }
+        else if(Angle < props->OuterAngle)
+        {
+            const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)};
+            ConeGain = lerp(1.0f, props->OuterGain, scale);
+            ConeHF = lerp(1.0f, props->OuterGainHF, scale);
+        }
+        else
+        {
+            ConeGain = props->OuterGain;
+            ConeHF = props->OuterGainHF;
+        }
+
+        DryGain.Base *= ConeGain;
+        if(props->DryGainHFAuto)
+            DryGain.HF *= ConeHF;
+        if(props->WetGainAuto)
+            std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
+                [ConeGain](GainTriplet &gain) noexcept -> void { gain.Base *= ConeGain; });
+        if(props->WetGainHFAuto)
+            std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
+                [ConeHF](GainTriplet &gain) noexcept -> void { gain.HF *= ConeHF; });
+    }
+
+    /* Apply gain and frequency filters */
+    DryGain.Base = minf(clampf(DryGain.Base, props->MinGain, props->MaxGain) * props->Direct.Gain *
+        context->mParams.Gain, GainMixMax);
+    DryGain.HF *= props->Direct.GainHF;
+    DryGain.LF *= props->Direct.GainLF;
+    for(uint i{0};i < NumSends;i++)
+    {
+        WetGain[i].Base = minf(clampf(WetGain[i].Base, props->MinGain, props->MaxGain) *
+            props->Send[i].Gain * context->mParams.Gain, GainMixMax);
+        WetGain[i].HF *= props->Send[i].GainHF;
+        WetGain[i].LF *= props->Send[i].GainLF;
+    }
+
+    /* Distance-based air absorption and initial send decay. */
+    if(ClampedDist > props->RefDistance && props->RolloffFactor > 0.0f)
+    {
+        const float meters_base{(ClampedDist-props->RefDistance) * props->RolloffFactor *
+            context->mParams.MetersPerUnit};
+        if(props->AirAbsorptionFactor > 0.0f)
+        {
+            const float hfattn{std::pow(AirAbsorbGainHF, meters_base*props->AirAbsorptionFactor)};
+            DryGain.HF *= hfattn;
+            std::for_each(std::begin(WetGain), std::begin(WetGain)+NumSends,
+                [hfattn](GainTriplet &gain) noexcept -> void { gain.HF *= hfattn; });
+        }
+
+        if(props->WetGainAuto)
+        {
+            /* Apply a decay-time transformation to the wet path, based on the
+             * source distance in meters. The initial decay of the reverb
+             * effect is calculated and applied to the wet path.
+             */
+            for(uint i{0};i < NumSends;i++)
+            {
+                if(!(DecayDistance[i].Base > 0.0f))
+                    continue;
+
+                const float gain{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].Base)};
+                WetGain[i].Base *= gain;
+                /* Yes, the wet path's air absorption is applied with
+                 * WetGainAuto on, rather than WetGainHFAuto.
+                 */
+                if(gain > 0.0f)
+                {
+                    float gainhf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].HF)};
+                    WetGain[i].HF *= minf(gainhf / gain, 1.0f);
+                    float gainlf{std::pow(ReverbDecayGain, meters_base/DecayDistance[i].LF)};
+                    WetGain[i].LF *= minf(gainlf / gain, 1.0f);
+                }
+            }
+        }
+    }
+
+
+    /* Initial source pitch */
+    float Pitch{props->Pitch};
+
+    /* Calculate velocity-based doppler effect */
+    float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor};
+    if(DopplerFactor > 0.0f)
+    {
+        const alu::Vector &lvelocity = context->mParams.Velocity;
+        float vss{Velocity.dot_product(ToSource) * -DopplerFactor};
+        float vls{lvelocity.dot_product(ToSource) * -DopplerFactor};
+
+        const float SpeedOfSound{context->mParams.SpeedOfSound};
+        if(!(vls < SpeedOfSound))
+        {
+            /* Listener moving away from the source at the speed of sound.
+             * Sound waves can't catch it.
+             */
+            Pitch = 0.0f;
+        }
+        else if(!(vss < SpeedOfSound))
+        {
+            /* Source moving toward the listener at the speed of sound. Sound
+             * waves bunch up to extreme frequencies.
+             */
+            Pitch = std::numeric_limits<float>::infinity();
+        }
+        else
+        {
+            /* Source and listener movement is nominal. Calculate the proper
+             * doppler shift.
+             */
+            Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss);
+        }
+    }
+
+    /* Adjust pitch based on the buffer and output frequencies, and calculate
+     * fixed-point stepping value.
+     */
+    Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
+    if(Pitch > float{MaxPitch})
+        voice->mStep = MaxPitch<<MixerFracBits;
+    else
+        voice->mStep = maxu(fastf2u(Pitch * MixerFracOne), 1);
+    voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
+
+    float spread{0.0f};
+    if(props->Radius > Distance)
+        spread = al::MathDefs<float>::Tau() - Distance/props->Radius*al::MathDefs<float>::Pi();
+    else if(Distance > 0.0f)
+        spread = std::asin(props->Radius/Distance) * 2.0f;
+
+    CalcPanningAndFilters(voice, ToSource[0], ToSource[1], ToSource[2]*ZScale,
+        Distance*context->mParams.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props,
+        context->mParams, Device);
+}
+
+void CalcSourceParams(Voice *voice, ALCcontext *context, bool force)
+{
+    VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
+    if(!props && !force) return;
+
+    if(props)
+    {
+        voice->mProps = *props;
+
+        AtomicReplaceHead(context->mFreeVoiceProps, props);
+    }
+
+    if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono
+            && voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D)
+        || voice->mProps.mSpatializeMode==SpatializeMode::Off
+        || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono))
+        CalcNonAttnSourceParams(voice, &voice->mProps, context);
+    else
+        CalcAttnSourceParams(voice, &voice->mProps, context);
+}
+
+
+void SendSourceStateEvent(ALCcontext *context, uint id, VChangeState state)
+{
+    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 = state;
+
+    ring->writeAdvance(1);
+}
+
+void ProcessVoiceChanges(ALCcontext *ctx)
+{
+    VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
+    VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
+    if(!next) return;
+
+    const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
+    do {
+        cur = next;
+
+        bool sendevt{false};
+        if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop)
+        {
+            if(Voice *voice{cur->mVoice})
+            {
+                voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+                voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+                /* A source ID indicates the voice was playing or paused, which
+                 * gets a reset/stop event.
+                 */
+                sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u;
+                Voice::State oldvstate{Voice::Playing};
+                voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
+                    std::memory_order_relaxed, std::memory_order_acquire);
+                voice->mPendingChange.store(false, std::memory_order_release);
+            }
+            /* Reset state change events are always sent, even if the voice is
+             * already stopped or even if there is no voice.
+             */
+            sendevt |= (cur->mState == VChangeState::Reset);
+        }
+        else if(cur->mState == VChangeState::Pause)
+        {
+            Voice *voice{cur->mVoice};
+            Voice::State oldvstate{Voice::Playing};
+            sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
+                std::memory_order_release, std::memory_order_acquire);
+        }
+        else if(cur->mState == VChangeState::Play)
+        {
+            /* NOTE: When playing a voice, sending a source state change event
+             * depends if there's an old voice to stop and if that stop is
+             * successful. If there is no old voice, a playing event is always
+             * sent. If there is an old voice, an event is sent only if the
+             * voice is already stopped.
+             */
+            if(Voice *oldvoice{cur->mOldVoice})
+            {
+                oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+                oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+                oldvoice->mSourceID.store(0u, std::memory_order_relaxed);
+                Voice::State oldvstate{Voice::Playing};
+                sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
+                    std::memory_order_relaxed, std::memory_order_acquire);
+                oldvoice->mPendingChange.store(false, std::memory_order_release);
+            }
+            else
+                sendevt = true;
+
+            Voice *voice{cur->mVoice};
+            voice->mPlayState.store(Voice::Playing, std::memory_order_release);
+        }
+        else if(cur->mState == VChangeState::Restart)
+        {
+            /* Restarting a voice never sends a source change event. */
+            Voice *oldvoice{cur->mOldVoice};
+            oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+            oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+            /* If there's no sourceID, the old voice finished so don't start
+             * the new one at its new offset.
+             */
+            if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u)
+            {
+                /* Otherwise, set the voice to stopping if it's not already (it
+                 * might already be, if paused), and play the new voice as
+                 * appropriate.
+                 */
+                Voice::State oldvstate{Voice::Playing};
+                oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping,
+                    std::memory_order_relaxed, std::memory_order_acquire);
+
+                Voice *voice{cur->mVoice};
+                voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing
+                    : Voice::Stopped, std::memory_order_release);
+            }
+            oldvoice->mPendingChange.store(false, std::memory_order_release);
+        }
+        if(sendevt && (enabledevt&EventType_SourceStateChange))
+            SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
+
+        next = cur->mNext.load(std::memory_order_acquire);
+    } while(next);
+    ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
+}
+
+void ProcessParamUpdates(ALCcontext *ctx, const EffectSlotArray &slots,
+    const al::span<Voice*> voices)
+{
+    ProcessVoiceChanges(ctx);
+
+    IncrementRef(ctx->mUpdateCount);
+    if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
+    {
+        bool force{CalcContextParams(ctx)};
+        force |= CalcListenerParams(ctx);
+        auto sorted_slots = const_cast<EffectSlot**>(slots.data() + slots.size());
+        for(EffectSlot *slot : slots)
+            force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
+
+        for(Voice *voice : voices)
+        {
+            /* Only update voices that have a source. */
+            if(voice->mSourceID.load(std::memory_order_relaxed) != 0)
+                CalcSourceParams(voice, ctx, force);
+        }
+    }
+    IncrementRef(ctx->mUpdateCount);
+}
+
+void ProcessContexts(ALCdevice *device, const uint SamplesToDo)
+{
+    ASSUME(SamplesToDo > 0);
+
+    for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
+    {
+        const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
+        const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
+
+        /* Process pending propery updates for objects on the context. */
+        ProcessParamUpdates(ctx, auxslots, voices);
+
+        /* Clear auxiliary effect slot mixing buffers. */
+        for(EffectSlot *slot : auxslots)
+        {
+            for(auto &buffer : slot->Wet.Buffer)
+                buffer.fill(0.0f);
+        }
+
+        /* Process voices that have a playing source. */
+        for(Voice *voice : voices)
+        {
+            const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
+            if(vstate != Voice::Stopped && vstate != Voice::Pending)
+                voice->mix(vstate, ctx, SamplesToDo);
+        }
+
+        /* Process effects. */
+        if(const size_t num_slots{auxslots.size()})
+        {
+            auto slots = auxslots.data();
+            auto slots_end = slots + num_slots;
+
+            /* Sort the slots into extra storage, so that effect slots come
+             * before their effect slot target (or their targets' target).
+             */
+            const al::span<EffectSlot*> sorted_slots{const_cast<EffectSlot**>(slots_end),
+                num_slots};
+            /* Skip sorting if it has already been done. */
+            if(!sorted_slots[0])
+            {
+                /* First, copy the slots to the sorted list, then partition the
+                 * sorted list so that all slots without a target slot go to
+                 * the end.
+                 */
+                std::copy(slots, slots_end, sorted_slots.begin());
+                auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(),
+                    [](const EffectSlot *slot) noexcept -> bool
+                    { return slot->Target != nullptr; });
+                /* There must be at least one slot without a slot target. */
+                assert(split_point != sorted_slots.end());
+
+                /* Simple case: no more than 1 slot has a target slot. Either
+                 * all slots go right to the output, or the remaining one must
+                 * target an already-partitioned slot.
+                 */
+                if(split_point - sorted_slots.begin() > 1)
+                {
+                    /* At least two slots target other slots. Starting from the
+                     * back of the sorted list, continue partitioning the front
+                     * of the list given each target until all targets are
+                     * accounted for. This ensures all slots without a target
+                     * go last, all slots directly targeting those last slots
+                     * go second-to-last, all slots directly targeting those
+                     * second-last slots go third-to-last, etc.
+                     */
+                    auto next_target = sorted_slots.end();
+                    do {
+                        /* This shouldn't happen, but if there's unsorted slots
+                         * left that don't target any sorted slots, they can't
+                         * contribute to the output, so leave them.
+                         */
+                        if UNLIKELY(next_target == split_point)
+                            break;
+
+                        --next_target;
+                        split_point = std::partition(sorted_slots.begin(), split_point,
+                            [next_target](const EffectSlot *slot) noexcept -> bool
+                            { return slot->Target != *next_target; });
+                    } while(split_point - sorted_slots.begin() > 1);
+                }
+            }
+
+            for(const EffectSlot *slot : sorted_slots)
+            {
+                EffectState *state{slot->mEffectState};
+                state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
+            }
+        }
+
+        /* Signal the event handler if there are any events to read. */
+        RingBuffer *ring{ctx->mAsyncEvents.get()};
+        if(ring->readSpace() > 0)
+            ctx->mEventSem.post();
+    }
+}
+
+
+void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t SamplesToDo,
+    const DistanceComp::ChanData *distcomp)
+{
+    ASSUME(SamplesToDo > 0);
+
+    for(auto &chanbuffer : Samples)
+    {
+        const float gain{distcomp->Gain};
+        const size_t base{distcomp->Length};
+        float *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
+        ++distcomp;
+
+        if(base < 1)
+            continue;
+
+        float *inout{al::assume_aligned<16>(chanbuffer.data())};
+        auto inout_end = inout + SamplesToDo;
+        if LIKELY(SamplesToDo >= base)
+        {
+            auto delay_end = std::rotate(inout, inout_end - base, inout_end);
+            std::swap_ranges(inout, delay_end, distbuf);
+        }
+        else
+        {
+            auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
+            std::rotate(distbuf, delay_start, distbuf + base);
+        }
+        std::transform(inout, inout_end, inout, std::bind(std::multiplies<float>{}, _1, gain));
+    }
+}
+
+void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed,
+    const float quant_scale, const size_t SamplesToDo)
+{
+    ASSUME(SamplesToDo > 0);
+
+    /* Dithering. Generate whitenoise (uniform distribution of random values
+     * between -1 and +1) and add it to the sample values, after scaling up to
+     * the desired quantization depth amd before rounding.
+     */
+    const float invscale{1.0f / quant_scale};
+    uint seed{*dither_seed};
+    auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float
+    {
+        float val{sample * quant_scale};
+        uint rng0{dither_rng(&seed)};
+        uint rng1{dither_rng(&seed)};
+        val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
+        return fast_roundf(val) * invscale;
+    };
+    for(FloatBufferLine &inout : Samples)
+        std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample);
+    *dither_seed = seed;
+}
+
+
+/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
+ * chokes on that given the inline specializations.
+ */
+template<typename T>
+inline T SampleConv(float) noexcept;
+
+template<> inline float SampleConv(float val) noexcept
+{ return val; }
+template<> inline int32_t SampleConv(float val) noexcept
+{
+    /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit.
+     * This means a normalized float has at most 25 bits of signed precision.
+     * When scaling and clamping for a signed 32-bit integer, these following
+     * values are the best a float can give.
+     */
+    return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f));
+}
+template<> inline int16_t SampleConv(float val) noexcept
+{ return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
+template<> inline int8_t SampleConv(float val) noexcept
+{ return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
+
+/* Define unsigned output variations. */
+template<> inline uint32_t SampleConv(float val) noexcept
+{ return static_cast<uint32_t>(SampleConv<int32_t>(val)) + 2147483648u; }
+template<> inline uint16_t SampleConv(float val) noexcept
+{ return static_cast<uint16_t>(SampleConv<int16_t>(val) + 32768); }
+template<> inline uint8_t SampleConv(float val) noexcept
+{ return static_cast<uint8_t>(SampleConv<int8_t>(val) + 128); }
+
+template<DevFmtType T>
+void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, const size_t Offset,
+    const size_t SamplesToDo, const size_t FrameStep)
+{
+    ASSUME(FrameStep > 0);
+    ASSUME(SamplesToDo > 0);
+
+    DevFmtType_t<T> *outbase = static_cast<DevFmtType_t<T>*>(OutBuffer) + Offset*FrameStep;
+    for(const FloatBufferLine &inbuf : InBuffer)
+    {
+        DevFmtType_t<T> *out{outbase++};
+        auto conv_sample = [FrameStep,&out](const float s) noexcept -> void
+        {
+            *out = SampleConv<DevFmtType_t<T>>(s);
+            out += FrameStep;
+        };
+        std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
+    }
+}
+
+} // namespace
+
+void ALCdevice::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep)
+{
+    FPUCtl mixer_mode{};
+    for(uint written{0u};written < numSamples;)
+    {
+        const uint samplesToDo{minu(numSamples-written, BufferLineSize)};
+
+        /* Clear main mixing buffers. */
+        for(FloatBufferLine &buffer : MixBuffer)
+            buffer.fill(0.0f);
+
+        /* Increment the mix count at the start (lsb should now be 1). */
+        IncrementRef(MixCount);
+
+        /* Process and mix each context's sources and effects. */
+        ProcessContexts(this, samplesToDo);
+
+        /* Increment the clock time. Every second's worth of samples is
+         * converted and added to clock base so that large sample counts don't
+         * overflow during conversion. This also guarantees a stable
+         * conversion.
+         */
+        SamplesDone += samplesToDo;
+        ClockBase += std::chrono::seconds{SamplesDone / Frequency};
+        SamplesDone %= Frequency;
+
+        /* Increment the mix count at the end (lsb should now be 0). */
+        IncrementRef(MixCount);
+
+        /* Apply any needed post-process for finalizing the Dry mix to the
+         * RealOut (Ambisonic decode, UHJ encode, etc).
+         */
+        postProcess(samplesToDo);
+
+        /* Apply compression, limiting sample amplitude if needed or desired. */
+        if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
+
+        /* Apply delays and attenuation for mismatched speaker distances. */
+        if(ChannelDelays)
+            ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data());
+
+        /* Apply dithering. The compressor should have left enough headroom for
+         * the dither noise to not saturate.
+         */
+        if(DitherDepth > 0.0f)
+            ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
+
+        if LIKELY(outBuffer)
+        {
+            /* Finally, interleave and convert samples, writing to the device's
+             * output buffer.
+             */
+            switch(FmtType)
+            {
+#define HANDLE_WRITE(T) case T:                                               \
+    Write<T>(RealOut.Buffer, outBuffer, written, samplesToDo, frameStep); break;
+            HANDLE_WRITE(DevFmtByte)
+            HANDLE_WRITE(DevFmtUByte)
+            HANDLE_WRITE(DevFmtShort)
+            HANDLE_WRITE(DevFmtUShort)
+            HANDLE_WRITE(DevFmtInt)
+            HANDLE_WRITE(DevFmtUInt)
+            HANDLE_WRITE(DevFmtFloat)
+#undef HANDLE_WRITE
+            }
+        }
+
+        written += samplesToDo;
+    }
+}
+
+void ALCdevice::handleDisconnect(const char *msg, ...)
+{
+    if(!Connected.exchange(false, std::memory_order_acq_rel))
+        return;
+
+    AsyncEvent evt{EventType_Disconnected};
+
+    va_list args;
+    va_start(args, msg);
+    int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)};
+    va_end(args);
+
+    if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.disconnect.msg))
+        evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0;
+
+    IncrementRef(MixCount);
+    for(ALCcontext *ctx : *mContexts.load())
+    {
+        const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
+        if((enabledevt&EventType_Disconnected))
+        {
+            RingBuffer *ring{ctx->mAsyncEvents.get()};
+            auto evt_data = ring->getWriteVector().first;
+            if(evt_data.len > 0)
+            {
+                ::new(evt_data.buf) AsyncEvent{evt};
+                ring->writeAdvance(1);
+                ctx->mEventSem.post();
+            }
+        }
+
+        auto voicelist = ctx->getVoicesSpanAcquired();
+        auto stop_voice = [](Voice *voice) -> void
+        {
+            voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+            voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+            voice->mSourceID.store(0u, std::memory_order_relaxed);
+            voice->mPlayState.store(Voice::Stopped, std::memory_order_release);
+        };
+        std::for_each(voicelist.begin(), voicelist.end(), stop_voice);
+    }
+    IncrementRef(MixCount);
+}

+ 141 - 0
Engine/lib/openal-soft/Alc/alu.h

@@ -0,0 +1,141 @@
+#ifndef ALU_H
+#define ALU_H
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <type_traits>
+
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/devformat.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;
+
+
+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 HrtfRequestMode {
+    Hrtf_Default = 0,
+    Hrtf_Enable = 1,
+    Hrtf_Disable = 2,
+};
+
+void aluInit(void);
+
+void aluInitMixer(void);
+
+/* 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 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

+ 0 - 566
Engine/lib/openal-soft/Alc/ambdec.c

@@ -1,566 +0,0 @@
-
-#include "config.h"
-
-#include "ambdec.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "compat.h"
-
-
-static char *lstrip(char *line)
-{
-    while(isspace(line[0]))
-        line++;
-    return line;
-}
-
-static char *rstrip(char *line)
-{
-    size_t len = strlen(line);
-    while(len > 0 && isspace(line[len-1]))
-        len--;
-    line[len] = 0;
-    return line;
-}
-
-static int readline(FILE *f, char **output, size_t *maxlen)
-{
-    size_t len = 0;
-    int c;
-
-    while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n'))
-        ;
-    if(c == EOF)
-        return 0;
-
-    do {
-        if(len+1 >= *maxlen)
-        {
-            void *temp = NULL;
-            size_t newmax;
-
-            newmax = (*maxlen ? (*maxlen)<<1 : 32);
-            if(newmax > *maxlen)
-                temp = realloc(*output, newmax);
-            if(!temp)
-            {
-                ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen);
-                return 0;
-            }
-
-            *output = temp;
-            *maxlen = newmax;
-        }
-        (*output)[len++] = c;
-        (*output)[len] = '\0';
-    } while((c=fgetc(f)) != EOF && c != '\r' && c != '\n');
-
-    return 1;
-}
-
-
-/* Custom strtok_r, since we can't rely on it existing. */
-static char *my_strtok_r(char *str, const char *delim, char **saveptr)
-{
-    /* Sanity check and update internal pointer. */
-    if(!saveptr || !delim) return NULL;
-    if(str) *saveptr = str;
-    str = *saveptr;
-
-    /* Nothing more to do with this string. */
-    if(!str) return NULL;
-
-    /* Find the first non-delimiter character. */
-    while(*str != '\0' && strchr(delim, *str) != NULL)
-        str++;
-    if(*str == '\0')
-    {
-        /* End of string. */
-        *saveptr = NULL;
-        return NULL;
-    }
-
-    /* Find the next delimiter character. */
-    *saveptr = strpbrk(str, delim);
-    if(*saveptr) *((*saveptr)++) = '\0';
-
-    return str;
-}
-
-static char *read_int(ALint *num, const char *line, int base)
-{
-    char *end;
-    *num = strtol(line, &end, base);
-    if(end && *end != '\0')
-        end = lstrip(end);
-    return end;
-}
-
-static char *read_uint(ALuint *num, const char *line, int base)
-{
-    char *end;
-    *num = strtoul(line, &end, base);
-    if(end && *end != '\0')
-        end = lstrip(end);
-    return end;
-}
-
-static char *read_float(ALfloat *num, const char *line)
-{
-    char *end;
-#ifdef HAVE_STRTOF
-    *num = strtof(line, &end);
-#else
-    *num = (ALfloat)strtod(line, &end);
-#endif
-    if(end && *end != '\0')
-        end = lstrip(end);
-    return end;
-}
-
-
-char *read_clipped_line(FILE *f, char **buffer, size_t *maxlen)
-{
-    while(readline(f, buffer, maxlen))
-    {
-        char *line, *comment;
-
-        line = lstrip(*buffer);
-        comment = strchr(line, '#');
-        if(comment) *(comment++) = 0;
-
-        line = rstrip(line);
-        if(line[0]) return line;
-    }
-    return NULL;
-}
-
-static int load_ambdec_speakers(AmbDecConf *conf, FILE *f, char **buffer, size_t *maxlen, char **saveptr)
-{
-    ALsizei cur = 0;
-    while(cur < conf->NumSpeakers)
-    {
-        const char *cmd = my_strtok_r(NULL, " \t", saveptr);
-        if(!cmd)
-        {
-            char *line = read_clipped_line(f, buffer, maxlen);
-            if(!line)
-            {
-                ERR("Unexpected end of file\n");
-                return 0;
-            }
-            cmd = my_strtok_r(line, " \t", saveptr);
-        }
-
-        if(strcmp(cmd, "add_spkr") == 0)
-        {
-            const char *name = my_strtok_r(NULL, " \t", saveptr);
-            const char *dist = my_strtok_r(NULL, " \t", saveptr);
-            const char *az = my_strtok_r(NULL, " \t", saveptr);
-            const char *elev = my_strtok_r(NULL, " \t", saveptr);
-            const char *conn = my_strtok_r(NULL, " \t", saveptr);
-
-            if(!name) WARN("Name not specified for speaker %u\n", cur+1);
-            else alstr_copy_cstr(&conf->Speakers[cur].Name, name);
-            if(!dist) WARN("Distance not specified for speaker %u\n", cur+1);
-            else read_float(&conf->Speakers[cur].Distance, dist);
-            if(!az) WARN("Azimuth not specified for speaker %u\n", cur+1);
-            else read_float(&conf->Speakers[cur].Azimuth, az);
-            if(!elev) WARN("Elevation not specified for speaker %u\n", cur+1);
-            else read_float(&conf->Speakers[cur].Elevation, elev);
-            if(!conn) TRACE("Connection not specified for speaker %u\n", cur+1);
-            else alstr_copy_cstr(&conf->Speakers[cur].Connection, conn);
-
-            cur++;
-        }
-        else
-        {
-            ERR("Unexpected speakers command: %s\n", cmd);
-            return 0;
-        }
-
-        cmd = my_strtok_r(NULL, " \t", saveptr);
-        if(cmd)
-        {
-            ERR("Unexpected junk on line: %s\n", cmd);
-            return 0;
-        }
-    }
-
-    return 1;
-}
-
-static int load_ambdec_matrix(ALfloat *gains, ALfloat (*matrix)[MAX_AMBI_COEFFS], ALsizei maxrow, FILE *f, char **buffer, size_t *maxlen, char **saveptr)
-{
-    int gotgains = 0;
-    ALsizei cur = 0;
-    while(cur < maxrow)
-    {
-        const char *cmd = my_strtok_r(NULL, " \t", saveptr);
-        if(!cmd)
-        {
-            char *line = read_clipped_line(f, buffer, maxlen);
-            if(!line)
-            {
-                ERR("Unexpected end of file\n");
-                return 0;
-            }
-            cmd = my_strtok_r(line, " \t", saveptr);
-        }
-
-        if(strcmp(cmd, "order_gain") == 0)
-        {
-            ALuint curgain = 0;
-            char *line;
-            while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL)
-            {
-                ALfloat value;
-                line = read_float(&value, line);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk on gain %u: %s\n", curgain+1, line);
-                    return 0;
-                }
-                if(curgain < MAX_AMBI_ORDER+1)
-                    gains[curgain] = value;
-                curgain++;
-            }
-            while(curgain < MAX_AMBI_ORDER+1)
-                gains[curgain++] = 0.0f;
-            gotgains = 1;
-        }
-        else if(strcmp(cmd, "add_row") == 0)
-        {
-            ALuint curidx = 0;
-            char *line;
-            while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL)
-            {
-                ALfloat value;
-                line = read_float(&value, line);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk on matrix element %ux%u: %s\n", cur, curidx, line);
-                    return 0;
-                }
-                if(curidx < MAX_AMBI_COEFFS)
-                    matrix[cur][curidx] = value;
-                curidx++;
-            }
-            while(curidx < MAX_AMBI_COEFFS)
-                matrix[cur][curidx++] = 0.0f;
-            cur++;
-        }
-        else
-        {
-            ERR("Unexpected speakers command: %s\n", cmd);
-            return 0;
-        }
-
-        cmd = my_strtok_r(NULL, " \t", saveptr);
-        if(cmd)
-        {
-            ERR("Unexpected junk on line: %s\n", cmd);
-            return 0;
-        }
-    }
-
-    if(!gotgains)
-    {
-        ERR("Matrix order_gain not specified\n");
-        return 0;
-    }
-
-    return 1;
-}
-
-void ambdec_init(AmbDecConf *conf)
-{
-    ALsizei i;
-
-    memset(conf, 0, sizeof(*conf));
-    AL_STRING_INIT(conf->Description);
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        AL_STRING_INIT(conf->Speakers[i].Name);
-        AL_STRING_INIT(conf->Speakers[i].Connection);
-    }
-}
-
-void ambdec_deinit(AmbDecConf *conf)
-{
-    ALsizei i;
-
-    alstr_reset(&conf->Description);
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        alstr_reset(&conf->Speakers[i].Name);
-        alstr_reset(&conf->Speakers[i].Connection);
-    }
-    memset(conf, 0, sizeof(*conf));
-}
-
-int ambdec_load(AmbDecConf *conf, const char *fname)
-{
-    char *buffer = NULL;
-    size_t maxlen = 0;
-    char *line;
-    FILE *f;
-
-    f = al_fopen(fname, "r");
-    if(!f)
-    {
-        ERR("Failed to open: %s\n", fname);
-        return 0;
-    }
-
-    while((line=read_clipped_line(f, &buffer, &maxlen)) != NULL)
-    {
-        char *saveptr;
-        char *command;
-
-        command = my_strtok_r(line, "/ \t", &saveptr);
-        if(!command)
-        {
-            ERR("Malformed line: %s\n", line);
-            goto fail;
-        }
-
-        if(strcmp(command, "description") == 0)
-        {
-            char *value = my_strtok_r(NULL, "", &saveptr);
-            alstr_copy_cstr(&conf->Description, lstrip(value));
-        }
-        else if(strcmp(command, "version") == 0)
-        {
-            line = my_strtok_r(NULL, "", &saveptr);
-            line = read_uint(&conf->Version, line, 10);
-            if(line && *line != '\0')
-            {
-                ERR("Extra junk after version: %s\n", line);
-                goto fail;
-            }
-            if(conf->Version != 3)
-            {
-                ERR("Unsupported version: %u\n", conf->Version);
-                goto fail;
-            }
-        }
-        else if(strcmp(command, "dec") == 0)
-        {
-            const char *dec = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(strcmp(dec, "chan_mask") == 0)
-            {
-                line = my_strtok_r(NULL, "", &saveptr);
-                line = read_uint(&conf->ChanMask, line, 16);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk after mask: %s\n", line);
-                    goto fail;
-                }
-            }
-            else if(strcmp(dec, "freq_bands") == 0)
-            {
-                line = my_strtok_r(NULL, "", &saveptr);
-                line = read_uint(&conf->FreqBands, line, 10);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk after freq_bands: %s\n", line);
-                    goto fail;
-                }
-                if(conf->FreqBands != 1 && conf->FreqBands != 2)
-                {
-                    ERR("Invalid freq_bands value: %u\n", conf->FreqBands);
-                    goto fail;
-                }
-            }
-            else if(strcmp(dec, "speakers") == 0)
-            {
-                line = my_strtok_r(NULL, "", &saveptr);
-                line = read_int(&conf->NumSpeakers, line, 10);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk after speakers: %s\n", line);
-                    goto fail;
-                }
-                if(conf->NumSpeakers > MAX_OUTPUT_CHANNELS)
-                {
-                    ERR("Unsupported speaker count: %u\n", conf->NumSpeakers);
-                    goto fail;
-                }
-            }
-            else if(strcmp(dec, "coeff_scale") == 0)
-            {
-                line = my_strtok_r(NULL, " \t", &saveptr);
-                if(strcmp(line, "n3d") == 0)
-                    conf->CoeffScale = ADS_N3D;
-                else if(strcmp(line, "sn3d") == 0)
-                    conf->CoeffScale = ADS_SN3D;
-                else if(strcmp(line, "fuma") == 0)
-                    conf->CoeffScale = ADS_FuMa;
-                else
-                {
-                    ERR("Unsupported coeff scale: %s\n", line);
-                    goto fail;
-                }
-            }
-            else
-            {
-                ERR("Unexpected /dec option: %s\n", dec);
-                goto fail;
-            }
-        }
-        else if(strcmp(command, "opt") == 0)
-        {
-            const char *opt = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(strcmp(opt, "xover_freq") == 0)
-            {
-                line = my_strtok_r(NULL, "", &saveptr);
-                line = read_float(&conf->XOverFreq, line);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk after xover_freq: %s\n", line);
-                    goto fail;
-                }
-            }
-            else if(strcmp(opt, "xover_ratio") == 0)
-            {
-                line = my_strtok_r(NULL, "", &saveptr);
-                line = read_float(&conf->XOverRatio, line);
-                if(line && *line != '\0')
-                {
-                    ERR("Extra junk after xover_ratio: %s\n", line);
-                    goto fail;
-                }
-            }
-            else if(strcmp(opt, "input_scale") == 0 || strcmp(opt, "nfeff_comp") == 0 ||
-                    strcmp(opt, "delay_comp") == 0 || strcmp(opt, "level_comp") == 0)
-            {
-                /* Unused */
-                my_strtok_r(NULL, " \t", &saveptr);
-            }
-            else
-            {
-                ERR("Unexpected /opt option: %s\n", opt);
-                goto fail;
-            }
-        }
-        else if(strcmp(command, "speakers") == 0)
-        {
-            const char *value = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(strcmp(value, "{") != 0)
-            {
-                ERR("Expected { after %s command, got %s\n", command, value);
-                goto fail;
-            }
-            if(!load_ambdec_speakers(conf, f, &buffer, &maxlen, &saveptr))
-                goto fail;
-            value = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(!value)
-            {
-                line = read_clipped_line(f, &buffer, &maxlen);
-                if(!line)
-                {
-                    ERR("Unexpected end of file\n");
-                    goto fail;
-                }
-                value = my_strtok_r(line, "/ \t", &saveptr);
-            }
-            if(strcmp(value, "}") != 0)
-            {
-                ERR("Expected } after speaker definitions, got %s\n", value);
-                goto fail;
-            }
-        }
-        else if(strcmp(command, "lfmatrix") == 0 || strcmp(command, "hfmatrix") == 0 ||
-                strcmp(command, "matrix") == 0)
-        {
-            const char *value = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(strcmp(value, "{") != 0)
-            {
-                ERR("Expected { after %s command, got %s\n", command, value);
-                goto fail;
-            }
-            if(conf->FreqBands == 1)
-            {
-                if(strcmp(command, "matrix") != 0)
-                {
-                    ERR("Unexpected \"%s\" type for a single-band decoder\n", command);
-                    goto fail;
-                }
-                if(!load_ambdec_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers,
-                                       f, &buffer, &maxlen, &saveptr))
-                    goto fail;
-            }
-            else
-            {
-                if(strcmp(command, "lfmatrix") == 0)
-                {
-                    if(!load_ambdec_matrix(conf->LFOrderGain, conf->LFMatrix, conf->NumSpeakers,
-                                           f, &buffer, &maxlen, &saveptr))
-                        goto fail;
-                }
-                else if(strcmp(command, "hfmatrix") == 0)
-                {
-                    if(!load_ambdec_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers,
-                                           f, &buffer, &maxlen, &saveptr))
-                        goto fail;
-                }
-                else
-                {
-                    ERR("Unexpected \"%s\" type for a dual-band decoder\n", command);
-                    goto fail;
-                }
-            }
-            value = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(!value)
-            {
-                line = read_clipped_line(f, &buffer, &maxlen);
-                if(!line)
-                {
-                    ERR("Unexpected end of file\n");
-                    goto fail;
-                }
-                value = my_strtok_r(line, "/ \t", &saveptr);
-            }
-            if(strcmp(value, "}") != 0)
-            {
-                ERR("Expected } after matrix definitions, got %s\n", value);
-                goto fail;
-            }
-        }
-        else if(strcmp(command, "end") == 0)
-        {
-            line = my_strtok_r(NULL, "/ \t", &saveptr);
-            if(line)
-            {
-                ERR("Unexpected junk on end: %s\n", line);
-                goto fail;
-            }
-
-            fclose(f);
-            free(buffer);
-            return 1;
-        }
-        else
-        {
-            ERR("Unexpected command: %s\n", command);
-            goto fail;
-        }
-
-        line = my_strtok_r(NULL, "/ \t", &saveptr);
-        if(line)
-        {
-            ERR("Unexpected junk on line: %s\n", line);
-            goto fail;
-        }
-    }
-    ERR("Unexpected end of file\n");
-
-fail:
-    fclose(f);
-    free(buffer);
-    return 0;
-}

+ 0 - 46
Engine/lib/openal-soft/Alc/ambdec.h

@@ -1,46 +0,0 @@
-#ifndef AMBDEC_H
-#define AMBDEC_H
-
-#include "alstring.h"
-#include "alMain.h"
-
-/* Helpers to read .ambdec configuration files. */
-
-enum AmbDecScaleType {
-    ADS_N3D,
-    ADS_SN3D,
-    ADS_FuMa,
-};
-typedef struct AmbDecConf {
-    al_string Description;
-    ALuint Version; /* Must be 3 */
-
-    ALuint ChanMask;
-    ALuint FreqBands; /* Must be 1 or 2 */
-    ALsizei NumSpeakers;
-    enum AmbDecScaleType CoeffScale;
-
-    ALfloat XOverFreq;
-    ALfloat XOverRatio;
-
-    struct {
-        al_string Name;
-        ALfloat Distance;
-        ALfloat Azimuth;
-        ALfloat Elevation;
-        al_string Connection;
-    } Speakers[MAX_OUTPUT_CHANNELS];
-
-    /* Unused when FreqBands == 1 */
-    ALfloat LFOrderGain[MAX_AMBI_ORDER+1];
-    ALfloat LFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
-
-    ALfloat HFOrderGain[MAX_AMBI_ORDER+1];
-    ALfloat HFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
-} AmbDecConf;
-
-void ambdec_init(AmbDecConf *conf);
-void ambdec_deinit(AmbDecConf *conf);
-int ambdec_load(AmbDecConf *conf, const char *fname);
-
-#endif /* AMBDEC_H */

+ 49 - 0
Engine/lib/openal-soft/Alc/async_event.h

@@ -0,0 +1,49 @@
+#ifndef ALC_EVENT_H
+#define ALC_EVENT_H
+
+#include "almalloc.h"
+
+struct EffectState;
+enum class VChangeState;
+
+using uint = unsigned int;
+
+
+enum {
+    /* End event thread processing. */
+    EventType_KillThread = 0,
+
+    /* User event types. */
+    EventType_SourceStateChange = 1<<0,
+    EventType_BufferCompleted   = 1<<1,
+    EventType_Disconnected      = 1<<2,
+
+    /* Internal events. */
+    EventType_ReleaseEffectState = 65536,
+};
+
+struct AsyncEvent {
+    uint EnumType{0u};
+    union {
+        char dummy;
+        struct {
+            uint id;
+            VChangeState state;
+        } srcstate;
+        struct {
+            uint id;
+            uint count;
+        } bufcomp;
+        struct {
+            char msg[244];
+        } disconnect;
+        EffectState *mEffectState;
+    } u{};
+
+    AsyncEvent() noexcept = default;
+    constexpr AsyncEvent(uint type) noexcept : EnumType{type} { }
+
+    DISABLE_ALLOC()
+};
+
+#endif

+ 0 - 1456
Engine/lib/openal-soft/Alc/backends/alsa.c

@@ -1,1456 +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 <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <alsa/asoundlib.h>
-
-
-static const ALCchar alsaDevice[] = "ALSA Default";
-
-
-#ifdef HAVE_DYNLOAD
-#define ALSA_FUNCS(MAGIC)                                                     \
-    MAGIC(snd_strerror);                                                      \
-    MAGIC(snd_pcm_open);                                                      \
-    MAGIC(snd_pcm_close);                                                     \
-    MAGIC(snd_pcm_nonblock);                                                  \
-    MAGIC(snd_pcm_frames_to_bytes);                                           \
-    MAGIC(snd_pcm_bytes_to_frames);                                           \
-    MAGIC(snd_pcm_hw_params_malloc);                                          \
-    MAGIC(snd_pcm_hw_params_free);                                            \
-    MAGIC(snd_pcm_hw_params_any);                                             \
-    MAGIC(snd_pcm_hw_params_current);                                         \
-    MAGIC(snd_pcm_hw_params_set_access);                                      \
-    MAGIC(snd_pcm_hw_params_set_format);                                      \
-    MAGIC(snd_pcm_hw_params_set_channels);                                    \
-    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_current);                                         \
-    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);                                                    \
-    MAGIC(snd_pcm_reset);                                                     \
-    MAGIC(snd_pcm_wait);                                                      \
-    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);                                                     \
-    MAGIC(snd_pcm_writei);                                                    \
-    MAGIC(snd_pcm_drain);                                                     \
-    MAGIC(snd_pcm_drop);                                                      \
-    MAGIC(snd_pcm_recover);                                                   \
-    MAGIC(snd_pcm_info_malloc);                                               \
-    MAGIC(snd_pcm_info_free);                                                 \
-    MAGIC(snd_pcm_info_set_device);                                           \
-    MAGIC(snd_pcm_info_set_subdevice);                                        \
-    MAGIC(snd_pcm_info_set_stream);                                           \
-    MAGIC(snd_pcm_info_get_name);                                             \
-    MAGIC(snd_ctl_pcm_next_device);                                           \
-    MAGIC(snd_ctl_pcm_info);                                                  \
-    MAGIC(snd_ctl_open);                                                      \
-    MAGIC(snd_ctl_close);                                                     \
-    MAGIC(snd_ctl_card_info_malloc);                                          \
-    MAGIC(snd_ctl_card_info_free);                                            \
-    MAGIC(snd_ctl_card_info);                                                 \
-    MAGIC(snd_ctl_card_info_get_name);                                        \
-    MAGIC(snd_ctl_card_info_get_id);                                          \
-    MAGIC(snd_card_next);                                                     \
-    MAGIC(snd_config_update_free_global)
-
-static void *alsa_handle;
-#define MAKE_FUNC(f) static __typeof(f) * p##f
-ALSA_FUNCS(MAKE_FUNC);
-#undef MAKE_FUNC
-
-#define snd_strerror psnd_strerror
-#define snd_pcm_open psnd_pcm_open
-#define snd_pcm_close psnd_pcm_close
-#define snd_pcm_nonblock psnd_pcm_nonblock
-#define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes
-#define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames
-#define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc
-#define snd_pcm_hw_params_free psnd_pcm_hw_params_free
-#define snd_pcm_hw_params_any psnd_pcm_hw_params_any
-#define snd_pcm_hw_params_current psnd_pcm_hw_params_current
-#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access
-#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format
-#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels
-#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near
-#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near
-#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate
-#define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample
-#define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near
-#define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near
-#define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near
-#define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near
-#define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min
-#define snd_pcm_hw_params_get_buffer_time_min psnd_pcm_hw_params_get_buffer_time_min
-#define snd_pcm_hw_params_get_buffer_time_max psnd_pcm_hw_params_get_buffer_time_max
-#define snd_pcm_hw_params_get_period_time_min psnd_pcm_hw_params_get_period_time_min
-#define snd_pcm_hw_params_get_period_time_max psnd_pcm_hw_params_get_period_time_max
-#define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size
-#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size
-#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access
-#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods
-#define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format
-#define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels
-#define snd_pcm_hw_params psnd_pcm_hw_params
-#define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc
-#define snd_pcm_sw_params_current psnd_pcm_sw_params_current
-#define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min
-#define snd_pcm_sw_params_set_stop_threshold psnd_pcm_sw_params_set_stop_threshold
-#define snd_pcm_sw_params psnd_pcm_sw_params
-#define snd_pcm_sw_params_free psnd_pcm_sw_params_free
-#define snd_pcm_prepare psnd_pcm_prepare
-#define snd_pcm_start psnd_pcm_start
-#define snd_pcm_resume psnd_pcm_resume
-#define snd_pcm_reset psnd_pcm_reset
-#define snd_pcm_wait psnd_pcm_wait
-#define snd_pcm_delay psnd_pcm_delay
-#define snd_pcm_state psnd_pcm_state
-#define snd_pcm_avail_update psnd_pcm_avail_update
-#define snd_pcm_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
-#define snd_pcm_writei psnd_pcm_writei
-#define snd_pcm_drain psnd_pcm_drain
-#define snd_pcm_drop psnd_pcm_drop
-#define snd_pcm_recover psnd_pcm_recover
-#define snd_pcm_info_malloc psnd_pcm_info_malloc
-#define snd_pcm_info_free psnd_pcm_info_free
-#define snd_pcm_info_set_device psnd_pcm_info_set_device
-#define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice
-#define snd_pcm_info_set_stream psnd_pcm_info_set_stream
-#define snd_pcm_info_get_name psnd_pcm_info_get_name
-#define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device
-#define snd_ctl_pcm_info psnd_ctl_pcm_info
-#define snd_ctl_open psnd_ctl_open
-#define snd_ctl_close psnd_ctl_close
-#define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc
-#define snd_ctl_card_info_free psnd_ctl_card_info_free
-#define snd_ctl_card_info psnd_ctl_card_info
-#define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name
-#define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id
-#define snd_card_next psnd_card_next
-#define snd_config_update_free_global psnd_config_update_free_global
-#endif
-
-
-static ALCboolean alsa_load(void)
-{
-    ALCboolean error = ALC_FALSE;
-
-#ifdef HAVE_DYNLOAD
-    if(!alsa_handle)
-    {
-        al_string missing_funcs = AL_STRING_INIT_STATIC();
-
-        alsa_handle = LoadLib("libasound.so.2");
-        if(!alsa_handle)
-        {
-            WARN("Failed to load %s\n", "libasound.so.2");
-            return ALC_FALSE;
-        }
-
-        error = ALC_FALSE;
-#define LOAD_FUNC(f) do {                                                     \
-    p##f = GetSymbol(alsa_handle, #f);                                        \
-    if(p##f == NULL) {                                                        \
-        error = ALC_TRUE;                                                     \
-        alstr_append_cstr(&missing_funcs, "\n" #f);                           \
-    }                                                                         \
-} while(0)
-        ALSA_FUNCS(LOAD_FUNC);
-#undef LOAD_FUNC
-
-        if(error)
-        {
-            WARN("Missing expected functions:%s\n", alstr_get_cstr(missing_funcs));
-            CloseLib(alsa_handle);
-            alsa_handle = NULL;
-        }
-        alstr_reset(&missing_funcs);
-    }
-#endif
-
-    return !error;
-}
-
-
-typedef struct {
-    al_string name;
-    al_string device_name;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
-
-static vector_DevMap PlaybackDevices;
-static vector_DevMap CaptureDevices;
-
-static void clear_devlist(vector_DevMap *devlist)
-{
-#define FREE_DEV(i) do {                                                      \
-    AL_STRING_DEINIT((i)->name);                                              \
-    AL_STRING_DEINIT((i)->device_name);                                       \
-} while(0)
-    VECTOR_FOR_EACH(DevMap, *devlist, FREE_DEV);
-    VECTOR_RESIZE(*devlist, 0, 0);
-#undef FREE_DEV
-}
-
-
-static const char *prefix_name(snd_pcm_stream_t stream)
-{
-    assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
-    return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
-}
-
-static void probe_devices(snd_pcm_stream_t stream, vector_DevMap *DeviceList)
-{
-    const char *main_prefix = "plughw:";
-    snd_ctl_t *handle;
-    snd_ctl_card_info_t *info;
-    snd_pcm_info_t *pcminfo;
-    int card, err, dev;
-    DevMap entry;
-
-    clear_devlist(DeviceList);
-
-    snd_ctl_card_info_malloc(&info);
-    snd_pcm_info_malloc(&pcminfo);
-
-    AL_STRING_INIT(entry.name);
-    AL_STRING_INIT(entry.device_name);
-    alstr_copy_cstr(&entry.name, alsaDevice);
-    alstr_copy_cstr(&entry.device_name, GetConfigValue(
-        NULL, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture", "default"
-    ));
-    VECTOR_PUSH_BACK(*DeviceList, entry);
-
-    if(stream == SND_PCM_STREAM_PLAYBACK)
-    {
-        const char *customdevs, *sep, *next;
-        next = GetConfigValue(NULL, "alsa", "custom-devices", "");
-        while((customdevs=next) != NULL && customdevs[0])
-        {
-            next = strchr(customdevs, ';');
-            sep = strchr(customdevs, '=');
-            if(!sep)
-            {
-                al_string spec = AL_STRING_INIT_STATIC();
-                if(next)
-                    alstr_copy_range(&spec, customdevs, next++);
-                else
-                    alstr_copy_cstr(&spec, customdevs);
-                ERR("Invalid ALSA device specification \"%s\"\n", alstr_get_cstr(spec));
-                alstr_reset(&spec);
-                continue;
-            }
-
-            AL_STRING_INIT(entry.name);
-            AL_STRING_INIT(entry.device_name);
-            alstr_copy_range(&entry.name, customdevs, sep++);
-            if(next)
-                alstr_copy_range(&entry.device_name, sep, next++);
-            else
-                alstr_copy_cstr(&entry.device_name, sep);
-            TRACE("Got device \"%s\", \"%s\"\n", alstr_get_cstr(entry.name),
-                  alstr_get_cstr(entry.device_name));
-            VECTOR_PUSH_BACK(*DeviceList, entry);
-        }
-    }
-
-    card = -1;
-    if((err=snd_card_next(&card)) < 0)
-        ERR("Failed to find a card: %s\n", snd_strerror(err));
-    ConfigValueStr(NULL, "alsa", prefix_name(stream), &main_prefix);
-    while(card >= 0)
-    {
-        const char *card_prefix = main_prefix;
-        const char *cardname, *cardid;
-        char name[256];
-
-        snprintf(name, sizeof(name), "hw:%d", card);
-        if((err = snd_ctl_open(&handle, name, 0)) < 0)
-        {
-            ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
-            goto next_card;
-        }
-        if((err = snd_ctl_card_info(handle, info)) < 0)
-        {
-            ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
-            snd_ctl_close(handle);
-            goto next_card;
-        }
-
-        cardname = snd_ctl_card_info_get_name(info);
-        cardid = snd_ctl_card_info_get_id(info);
-
-        snprintf(name, sizeof(name), "%s-%s", prefix_name(stream), cardid);
-        ConfigValueStr(NULL, "alsa", name, &card_prefix);
-
-        dev = -1;
-        while(1)
-        {
-            const char *device_prefix = card_prefix;
-            const char *devname;
-            char device[128];
-
-            if(snd_ctl_pcm_next_device(handle, &dev) < 0)
-                ERR("snd_ctl_pcm_next_device failed\n");
-            if(dev < 0)
-                break;
-
-            snd_pcm_info_set_device(pcminfo, dev);
-            snd_pcm_info_set_subdevice(pcminfo, 0);
-            snd_pcm_info_set_stream(pcminfo, stream);
-            if((err = snd_ctl_pcm_info(handle, pcminfo)) < 0)
-            {
-                if(err != -ENOENT)
-                    ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
-                continue;
-            }
-
-            devname = snd_pcm_info_get_name(pcminfo);
-
-            snprintf(name, sizeof(name), "%s-%s-%d", prefix_name(stream), cardid, dev);
-            ConfigValueStr(NULL, "alsa", name, &device_prefix);
-
-            snprintf(name, sizeof(name), "%s, %s (CARD=%s,DEV=%d)",
-                     cardname, devname, cardid, dev);
-            snprintf(device, sizeof(device), "%sCARD=%s,DEV=%d",
-                     device_prefix, cardid, dev);
-
-            TRACE("Got device \"%s\", \"%s\"\n", name, device);
-            AL_STRING_INIT(entry.name);
-            AL_STRING_INIT(entry.device_name);
-            alstr_copy_cstr(&entry.name, name);
-            alstr_copy_cstr(&entry.device_name, device);
-            VECTOR_PUSH_BACK(*DeviceList, entry);
-        }
-        snd_ctl_close(handle);
-    next_card:
-        if(snd_card_next(&card) < 0) {
-            ERR("snd_card_next failed\n");
-            break;
-        }
-    }
-
-    snd_pcm_info_free(pcminfo);
-    snd_ctl_card_info_free(info);
-}
-
-
-static int verify_state(snd_pcm_t *handle)
-{
-    snd_pcm_state_t state = snd_pcm_state(handle);
-    int err;
-
-    switch(state)
-    {
-        case SND_PCM_STATE_OPEN:
-        case SND_PCM_STATE_SETUP:
-        case SND_PCM_STATE_PREPARED:
-        case SND_PCM_STATE_RUNNING:
-        case SND_PCM_STATE_DRAINING:
-        case SND_PCM_STATE_PAUSED:
-            /* All Okay */
-            break;
-
-        case SND_PCM_STATE_XRUN:
-            if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
-                return err;
-            break;
-        case SND_PCM_STATE_SUSPENDED:
-            if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
-                return err;
-            break;
-        case SND_PCM_STATE_DISCONNECTED:
-            return -ENODEV;
-    }
-
-    return state;
-}
-
-
-typedef struct ALCplaybackAlsa {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    snd_pcm_t *pcmHandle;
-
-    ALvoid *buffer;
-    ALsizei size;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCplaybackAlsa;
-
-static int ALCplaybackAlsa_mixerProc(void *ptr);
-static int ALCplaybackAlsa_mixerNoMMapProc(void *ptr);
-
-static void ALCplaybackAlsa_Construct(ALCplaybackAlsa *self, ALCdevice *device);
-static void ALCplaybackAlsa_Destruct(ALCplaybackAlsa *self);
-static ALCenum ALCplaybackAlsa_open(ALCplaybackAlsa *self, const ALCchar *name);
-static ALCboolean ALCplaybackAlsa_reset(ALCplaybackAlsa *self);
-static ALCboolean ALCplaybackAlsa_start(ALCplaybackAlsa *self);
-static void ALCplaybackAlsa_stop(ALCplaybackAlsa *self);
-static DECLARE_FORWARD2(ALCplaybackAlsa, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCplaybackAlsa, ALCbackend, ALCuint, availableSamples)
-static ClockLatency ALCplaybackAlsa_getClockLatency(ALCplaybackAlsa *self);
-static DECLARE_FORWARD(ALCplaybackAlsa, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCplaybackAlsa, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCplaybackAlsa)
-
-DEFINE_ALCBACKEND_VTABLE(ALCplaybackAlsa);
-
-
-static void ALCplaybackAlsa_Construct(ALCplaybackAlsa *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCplaybackAlsa, ALCbackend, self);
-
-    self->pcmHandle = NULL;
-    self->buffer = NULL;
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-void ALCplaybackAlsa_Destruct(ALCplaybackAlsa *self)
-{
-    if(self->pcmHandle)
-        snd_pcm_close(self->pcmHandle);
-    self->pcmHandle = NULL;
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCplaybackAlsa_mixerProc(void *ptr)
-{
-    ALCplaybackAlsa *self = (ALCplaybackAlsa*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const snd_pcm_channel_area_t *areas = NULL;
-    snd_pcm_uframes_t update_size, num_updates;
-    snd_pcm_sframes_t avail, commitres;
-    snd_pcm_uframes_t offset, frames;
-    char *WritePtr;
-    int err;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    update_size = device->UpdateSize;
-    num_updates = device->NumUpdates;
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
-    {
-        int state = verify_state(self->pcmHandle);
-        if(state < 0)
-        {
-            ERR("Invalid state detected: %s\n", snd_strerror(state));
-            ALCplaybackAlsa_lock(self);
-            aluHandleDisconnect(device, "Bad state: %s", snd_strerror(state));
-            ALCplaybackAlsa_unlock(self);
-            break;
-        }
-
-        avail = snd_pcm_avail_update(self->pcmHandle);
-        if(avail < 0)
-        {
-            ERR("available update failed: %s\n", snd_strerror(avail));
-            continue;
-        }
-
-        if((snd_pcm_uframes_t)avail > update_size*(num_updates+1))
-        {
-            WARN("available samples exceeds the buffer size\n");
-            snd_pcm_reset(self->pcmHandle);
-            continue;
-        }
-
-        // make sure there's frames to process
-        if((snd_pcm_uframes_t)avail < update_size)
-        {
-            if(state != SND_PCM_STATE_RUNNING)
-            {
-                err = snd_pcm_start(self->pcmHandle);
-                if(err < 0)
-                {
-                    ERR("start failed: %s\n", snd_strerror(err));
-                    continue;
-                }
-            }
-            if(snd_pcm_wait(self->pcmHandle, 1000) == 0)
-                ERR("Wait timeout... buffer size too low?\n");
-            continue;
-        }
-        avail -= avail%update_size;
-
-        // it is possible that contiguous areas are smaller, thus we use a loop
-        ALCplaybackAlsa_lock(self);
-        while(avail > 0)
-        {
-            frames = avail;
-
-            err = snd_pcm_mmap_begin(self->pcmHandle, &areas, &offset, &frames);
-            if(err < 0)
-            {
-                ERR("mmap begin error: %s\n", snd_strerror(err));
-                break;
-            }
-
-            WritePtr = (char*)areas->addr + (offset * areas->step / 8);
-            aluMixData(device, WritePtr, frames);
-
-            commitres = snd_pcm_mmap_commit(self->pcmHandle, offset, frames);
-            if(commitres < 0 || (commitres-frames) != 0)
-            {
-                ERR("mmap commit error: %s\n",
-                    snd_strerror(commitres >= 0 ? -EPIPE : commitres));
-                break;
-            }
-
-            avail -= frames;
-        }
-        ALCplaybackAlsa_unlock(self);
-    }
-
-    return 0;
-}
-
-static int ALCplaybackAlsa_mixerNoMMapProc(void *ptr)
-{
-    ALCplaybackAlsa *self = (ALCplaybackAlsa*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    snd_pcm_uframes_t update_size, num_updates;
-    snd_pcm_sframes_t avail;
-    char *WritePtr;
-    int err;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    update_size = device->UpdateSize;
-    num_updates = device->NumUpdates;
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
-    {
-        int state = verify_state(self->pcmHandle);
-        if(state < 0)
-        {
-            ERR("Invalid state detected: %s\n", snd_strerror(state));
-            ALCplaybackAlsa_lock(self);
-            aluHandleDisconnect(device, "Bad state: %s", snd_strerror(state));
-            ALCplaybackAlsa_unlock(self);
-            break;
-        }
-
-        avail = snd_pcm_avail_update(self->pcmHandle);
-        if(avail < 0)
-        {
-            ERR("available update failed: %s\n", snd_strerror(avail));
-            continue;
-        }
-
-        if((snd_pcm_uframes_t)avail > update_size*num_updates)
-        {
-            WARN("available samples exceeds the buffer size\n");
-            snd_pcm_reset(self->pcmHandle);
-            continue;
-        }
-
-        if((snd_pcm_uframes_t)avail < update_size)
-        {
-            if(state != SND_PCM_STATE_RUNNING)
-            {
-                err = snd_pcm_start(self->pcmHandle);
-                if(err < 0)
-                {
-                    ERR("start failed: %s\n", snd_strerror(err));
-                    continue;
-                }
-            }
-            if(snd_pcm_wait(self->pcmHandle, 1000) == 0)
-                ERR("Wait timeout... buffer size too low?\n");
-            continue;
-        }
-
-        ALCplaybackAlsa_lock(self);
-        WritePtr = self->buffer;
-        avail = snd_pcm_bytes_to_frames(self->pcmHandle, self->size);
-        aluMixData(device, WritePtr, avail);
-
-        while(avail > 0)
-        {
-            int ret = snd_pcm_writei(self->pcmHandle, WritePtr, avail);
-            switch (ret)
-            {
-            case -EAGAIN:
-                continue;
-#if ESTRPIPE != EPIPE
-            case -ESTRPIPE:
-#endif
-            case -EPIPE:
-            case -EINTR:
-                ret = snd_pcm_recover(self->pcmHandle, ret, 1);
-                if(ret < 0)
-                    avail = 0;
-                break;
-            default:
-                if (ret >= 0)
-                {
-                    WritePtr += snd_pcm_frames_to_bytes(self->pcmHandle, ret);
-                    avail -= ret;
-                }
-                break;
-            }
-            if (ret < 0)
-            {
-                ret = snd_pcm_prepare(self->pcmHandle);
-                if(ret < 0)
-                    break;
-            }
-        }
-        ALCplaybackAlsa_unlock(self);
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCplaybackAlsa_open(ALCplaybackAlsa *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const char *driver = NULL;
-    int err;
-
-    if(name)
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(PlaybackDevices) == 0)
-            probe_devices(SND_PCM_STREAM_PLAYBACK, &PlaybackDevices);
-
-#define MATCH_NAME(i)  (alstr_cmp_cstr((i)->name, name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(PlaybackDevices))
-            return ALC_INVALID_VALUE;
-        driver = alstr_get_cstr(iter->device_name);
-    }
-    else
-    {
-        name = alsaDevice;
-        driver = GetConfigValue(NULL, "alsa", "device", "default");
-    }
-
-    TRACE("Opening device \"%s\"\n", driver);
-    err = snd_pcm_open(&self->pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
-    if(err < 0)
-    {
-        ERR("Could not open playback device '%s': %s\n", driver, snd_strerror(err));
-        return ALC_OUT_OF_MEMORY;
-    }
-
-    /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
-    snd_config_update_free_global();
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCplaybackAlsa_reset(ALCplaybackAlsa *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    snd_pcm_uframes_t periodSizeInFrames;
-    unsigned int periodLen, bufferLen;
-    snd_pcm_sw_params_t *sp = NULL;
-    snd_pcm_hw_params_t *hp = NULL;
-    snd_pcm_format_t format = -1;
-    snd_pcm_access_t access;
-    unsigned int periods;
-    unsigned int rate;
-    const char *funcerr;
-    int allowmmap;
-    int dir;
-    int err;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            format = SND_PCM_FORMAT_S8;
-            break;
-        case DevFmtUByte:
-            format = SND_PCM_FORMAT_U8;
-            break;
-        case DevFmtShort:
-            format = SND_PCM_FORMAT_S16;
-            break;
-        case DevFmtUShort:
-            format = SND_PCM_FORMAT_U16;
-            break;
-        case DevFmtInt:
-            format = SND_PCM_FORMAT_S32;
-            break;
-        case DevFmtUInt:
-            format = SND_PCM_FORMAT_U32;
-            break;
-        case DevFmtFloat:
-            format = SND_PCM_FORMAT_FLOAT;
-            break;
-    }
-
-    allowmmap = GetConfigValueBool(alstr_get_cstr(device->DeviceName), "alsa", "mmap", 1);
-    periods = device->NumUpdates;
-    periodLen = (ALuint64)device->UpdateSize * 1000000 / device->Frequency;
-    bufferLen = periodLen * periods;
-    rate = device->Frequency;
-
-    snd_pcm_hw_params_malloc(&hp);
-#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error
-    CHECK(snd_pcm_hw_params_any(self->pcmHandle, hp));
-    /* set interleaved access */
-    if(!allowmmap || snd_pcm_hw_params_set_access(self->pcmHandle, hp, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0)
-    {
-        /* No mmap */
-        CHECK(snd_pcm_hw_params_set_access(self->pcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED));
-    }
-    /* test and set format (implicitly sets sample bits) */
-    if(snd_pcm_hw_params_test_format(self->pcmHandle, hp, format) < 0)
-    {
-        static const struct {
-            snd_pcm_format_t format;
-            enum DevFmtType fmttype;
-        } formatlist[] = {
-            { SND_PCM_FORMAT_FLOAT, DevFmtFloat  },
-            { SND_PCM_FORMAT_S32,   DevFmtInt    },
-            { SND_PCM_FORMAT_U32,   DevFmtUInt   },
-            { SND_PCM_FORMAT_S16,   DevFmtShort  },
-            { SND_PCM_FORMAT_U16,   DevFmtUShort },
-            { SND_PCM_FORMAT_S8,    DevFmtByte   },
-            { SND_PCM_FORMAT_U8,    DevFmtUByte  },
-        };
-        size_t k;
-
-        for(k = 0;k < COUNTOF(formatlist);k++)
-        {
-            format = formatlist[k].format;
-            if(snd_pcm_hw_params_test_format(self->pcmHandle, hp, format) >= 0)
-            {
-                device->FmtType = formatlist[k].fmttype;
-                break;
-            }
-        }
-    }
-    CHECK(snd_pcm_hw_params_set_format(self->pcmHandle, hp, format));
-    /* test and set channels (implicitly sets frame bits) */
-    if(snd_pcm_hw_params_test_channels(self->pcmHandle, hp, ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder)) < 0)
-    {
-        static const enum DevFmtChannels channellist[] = {
-            DevFmtStereo,
-            DevFmtQuad,
-            DevFmtX51,
-            DevFmtX71,
-            DevFmtMono,
-        };
-        size_t k;
-
-        for(k = 0;k < COUNTOF(channellist);k++)
-        {
-            if(snd_pcm_hw_params_test_channels(self->pcmHandle, hp, ChannelsFromDevFmt(channellist[k], 0)) >= 0)
-            {
-                device->FmtChans = channellist[k];
-                device->AmbiOrder = 0;
-                break;
-            }
-        }
-    }
-    CHECK(snd_pcm_hw_params_set_channels(self->pcmHandle, hp, ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder)));
-    /* set rate (implicitly constrains period/buffer parameters) */
-    if(!GetConfigValueBool(alstr_get_cstr(device->DeviceName), "alsa", "allow-resampler", 0) ||
-       !(device->Flags&DEVICE_FREQUENCY_REQUEST))
-    {
-        if(snd_pcm_hw_params_set_rate_resample(self->pcmHandle, hp, 0) < 0)
-            ERR("Failed to disable ALSA resampler\n");
-    }
-    else if(snd_pcm_hw_params_set_rate_resample(self->pcmHandle, hp, 1) < 0)
-        ERR("Failed to enable ALSA resampler\n");
-    CHECK(snd_pcm_hw_params_set_rate_near(self->pcmHandle, hp, &rate, NULL));
-    /* set buffer time (implicitly constrains period/buffer parameters) */
-    if((err=snd_pcm_hw_params_set_buffer_time_near(self->pcmHandle, hp, &bufferLen, NULL)) < 0)
-        ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err));
-    /* set period time (implicitly sets buffer size/bytes/time and period size/bytes) */
-    if((err=snd_pcm_hw_params_set_period_time_near(self->pcmHandle, hp, &periodLen, NULL)) < 0)
-        ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err));
-    /* install and prepare hardware configuration */
-    CHECK(snd_pcm_hw_params(self->pcmHandle, hp));
-    /* retrieve configuration info */
-    CHECK(snd_pcm_hw_params_get_access(hp, &access));
-    CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, NULL));
-    CHECK(snd_pcm_hw_params_get_periods(hp, &periods, &dir));
-    if(dir != 0)
-        WARN("Inexact period count: %u (%d)\n", periods, dir);
-
-    snd_pcm_hw_params_free(hp);
-    hp = NULL;
-    snd_pcm_sw_params_malloc(&sp);
-
-    CHECK(snd_pcm_sw_params_current(self->pcmHandle, sp));
-    CHECK(snd_pcm_sw_params_set_avail_min(self->pcmHandle, sp, periodSizeInFrames));
-    CHECK(snd_pcm_sw_params_set_stop_threshold(self->pcmHandle, sp, periodSizeInFrames*periods));
-    CHECK(snd_pcm_sw_params(self->pcmHandle, sp));
-#undef CHECK
-    snd_pcm_sw_params_free(sp);
-    sp = NULL;
-
-    device->NumUpdates = periods;
-    device->UpdateSize = periodSizeInFrames;
-    device->Frequency = rate;
-
-    SetDefaultChannelOrder(device);
-
-    return ALC_TRUE;
-
-error:
-    ERR("%s failed: %s\n", funcerr, snd_strerror(err));
-    if(hp) snd_pcm_hw_params_free(hp);
-    if(sp) snd_pcm_sw_params_free(sp);
-    return ALC_FALSE;
-}
-
-static ALCboolean ALCplaybackAlsa_start(ALCplaybackAlsa *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    int (*thread_func)(void*) = NULL;
-    snd_pcm_hw_params_t *hp = NULL;
-    snd_pcm_access_t access;
-    const char *funcerr;
-    int err;
-
-    snd_pcm_hw_params_malloc(&hp);
-#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error
-    CHECK(snd_pcm_hw_params_current(self->pcmHandle, hp));
-    /* retrieve configuration info */
-    CHECK(snd_pcm_hw_params_get_access(hp, &access));
-#undef CHECK
-    snd_pcm_hw_params_free(hp);
-    hp = NULL;
-
-    self->size = snd_pcm_frames_to_bytes(self->pcmHandle, device->UpdateSize);
-    if(access == SND_PCM_ACCESS_RW_INTERLEAVED)
-    {
-        self->buffer = al_malloc(16, self->size);
-        if(!self->buffer)
-        {
-            ERR("buffer malloc failed\n");
-            return ALC_FALSE;
-        }
-        thread_func = ALCplaybackAlsa_mixerNoMMapProc;
-    }
-    else
-    {
-        err = snd_pcm_prepare(self->pcmHandle);
-        if(err < 0)
-        {
-            ERR("snd_pcm_prepare(data->pcmHandle) failed: %s\n", snd_strerror(err));
-            return ALC_FALSE;
-        }
-        thread_func = ALCplaybackAlsa_mixerProc;
-    }
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, thread_func, self) != althrd_success)
-    {
-        ERR("Could not create playback thread\n");
-        al_free(self->buffer);
-        self->buffer = NULL;
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-
-error:
-    ERR("%s failed: %s\n", funcerr, snd_strerror(err));
-    if(hp) snd_pcm_hw_params_free(hp);
-    return ALC_FALSE;
-}
-
-static void ALCplaybackAlsa_stop(ALCplaybackAlsa *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &res);
-
-    al_free(self->buffer);
-    self->buffer = NULL;
-}
-
-static ClockLatency ALCplaybackAlsa_getClockLatency(ALCplaybackAlsa *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    snd_pcm_sframes_t delay = 0;
-    ClockLatency ret;
-    int err;
-
-    ALCplaybackAlsa_lock(self);
-    ret.ClockTime = GetDeviceClockTime(device);
-    if((err=snd_pcm_delay(self->pcmHandle, &delay)) < 0)
-    {
-        ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
-        delay = 0;
-    }
-    if(delay < 0) delay = 0;
-    ret.Latency = delay * DEVICE_CLOCK_RES / device->Frequency;
-    ALCplaybackAlsa_unlock(self);
-
-    return ret;
-}
-
-
-typedef struct ALCcaptureAlsa {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    snd_pcm_t *pcmHandle;
-
-    ALvoid *buffer;
-    ALsizei size;
-
-    ALboolean doCapture;
-    ll_ringbuffer_t *ring;
-
-    snd_pcm_sframes_t last_avail;
-} ALCcaptureAlsa;
-
-static void ALCcaptureAlsa_Construct(ALCcaptureAlsa *self, ALCdevice *device);
-static void ALCcaptureAlsa_Destruct(ALCcaptureAlsa *self);
-static ALCenum ALCcaptureAlsa_open(ALCcaptureAlsa *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCcaptureAlsa, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCcaptureAlsa_start(ALCcaptureAlsa *self);
-static void ALCcaptureAlsa_stop(ALCcaptureAlsa *self);
-static ALCenum ALCcaptureAlsa_captureSamples(ALCcaptureAlsa *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCcaptureAlsa_availableSamples(ALCcaptureAlsa *self);
-static ClockLatency ALCcaptureAlsa_getClockLatency(ALCcaptureAlsa *self);
-static DECLARE_FORWARD(ALCcaptureAlsa, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCcaptureAlsa, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCcaptureAlsa)
-
-DEFINE_ALCBACKEND_VTABLE(ALCcaptureAlsa);
-
-
-static void ALCcaptureAlsa_Construct(ALCcaptureAlsa *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCcaptureAlsa, ALCbackend, self);
-
-    self->pcmHandle = NULL;
-    self->buffer = NULL;
-    self->ring = NULL;
-}
-
-void ALCcaptureAlsa_Destruct(ALCcaptureAlsa *self)
-{
-    if(self->pcmHandle)
-        snd_pcm_close(self->pcmHandle);
-    self->pcmHandle = NULL;
-
-    al_free(self->buffer);
-    self->buffer = NULL;
-
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static ALCenum ALCcaptureAlsa_open(ALCcaptureAlsa *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const char *driver = NULL;
-    snd_pcm_hw_params_t *hp;
-    snd_pcm_uframes_t bufferSizeInFrames;
-    snd_pcm_uframes_t periodSizeInFrames;
-    ALboolean needring = AL_FALSE;
-    snd_pcm_format_t format = -1;
-    const char *funcerr;
-    int err;
-
-    if(name)
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(CaptureDevices) == 0)
-            probe_devices(SND_PCM_STREAM_CAPTURE, &CaptureDevices);
-
-#define MATCH_NAME(i)  (alstr_cmp_cstr((i)->name, name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(CaptureDevices))
-            return ALC_INVALID_VALUE;
-        driver = alstr_get_cstr(iter->device_name);
-    }
-    else
-    {
-        name = alsaDevice;
-        driver = GetConfigValue(NULL, "alsa", "capture", "default");
-    }
-
-    TRACE("Opening device \"%s\"\n", driver);
-    err = snd_pcm_open(&self->pcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
-    if(err < 0)
-    {
-        ERR("Could not open capture device '%s': %s\n", driver, snd_strerror(err));
-        return ALC_INVALID_VALUE;
-    }
-
-    /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
-    snd_config_update_free_global();
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            format = SND_PCM_FORMAT_S8;
-            break;
-        case DevFmtUByte:
-            format = SND_PCM_FORMAT_U8;
-            break;
-        case DevFmtShort:
-            format = SND_PCM_FORMAT_S16;
-            break;
-        case DevFmtUShort:
-            format = SND_PCM_FORMAT_U16;
-            break;
-        case DevFmtInt:
-            format = SND_PCM_FORMAT_S32;
-            break;
-        case DevFmtUInt:
-            format = SND_PCM_FORMAT_U32;
-            break;
-        case DevFmtFloat:
-            format = SND_PCM_FORMAT_FLOAT;
-            break;
-    }
-
-    funcerr = NULL;
-    bufferSizeInFrames = maxu(device->UpdateSize*device->NumUpdates,
-                              100*device->Frequency/1000);
-    periodSizeInFrames = minu(bufferSizeInFrames, 25*device->Frequency/1000);
-
-    snd_pcm_hw_params_malloc(&hp);
-#define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error
-    CHECK(snd_pcm_hw_params_any(self->pcmHandle, hp));
-    /* set interleaved access */
-    CHECK(snd_pcm_hw_params_set_access(self->pcmHandle, hp, SND_PCM_ACCESS_RW_INTERLEAVED));
-    /* set format (implicitly sets sample bits) */
-    CHECK(snd_pcm_hw_params_set_format(self->pcmHandle, hp, format));
-    /* set channels (implicitly sets frame bits) */
-    CHECK(snd_pcm_hw_params_set_channels(self->pcmHandle, hp, ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder)));
-    /* set rate (implicitly constrains period/buffer parameters) */
-    CHECK(snd_pcm_hw_params_set_rate(self->pcmHandle, hp, device->Frequency, 0));
-    /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
-    if(snd_pcm_hw_params_set_buffer_size_min(self->pcmHandle, hp, &bufferSizeInFrames) < 0)
-    {
-        TRACE("Buffer too large, using intermediate ring buffer\n");
-        needring = AL_TRUE;
-        CHECK(snd_pcm_hw_params_set_buffer_size_near(self->pcmHandle, hp, &bufferSizeInFrames));
-    }
-    /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
-    CHECK(snd_pcm_hw_params_set_period_size_near(self->pcmHandle, hp, &periodSizeInFrames, NULL));
-    /* install and prepare hardware configuration */
-    CHECK(snd_pcm_hw_params(self->pcmHandle, hp));
-    /* retrieve configuration info */
-    CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, NULL));
-#undef CHECK
-    snd_pcm_hw_params_free(hp);
-    hp = NULL;
-
-    if(needring)
-    {
-        self->ring = ll_ringbuffer_create(
-            device->UpdateSize*device->NumUpdates,
-            FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
-            false
-        );
-        if(!self->ring)
-        {
-            ERR("ring buffer create failed\n");
-            goto error2;
-        }
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-
-error:
-    ERR("%s failed: %s\n", funcerr, snd_strerror(err));
-    if(hp) snd_pcm_hw_params_free(hp);
-
-error2:
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-    snd_pcm_close(self->pcmHandle);
-    self->pcmHandle = NULL;
-
-    return ALC_INVALID_VALUE;
-}
-
-static ALCboolean ALCcaptureAlsa_start(ALCcaptureAlsa *self)
-{
-    int err = snd_pcm_start(self->pcmHandle);
-    if(err < 0)
-    {
-        ERR("start failed: %s\n", snd_strerror(err));
-        aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice, "Capture state failure: %s",
-                            snd_strerror(err));
-        return ALC_FALSE;
-    }
-
-    self->doCapture = AL_TRUE;
-    return ALC_TRUE;
-}
-
-static void ALCcaptureAlsa_stop(ALCcaptureAlsa *self)
-{
-    ALCuint avail;
-    int err;
-
-    /* OpenAL requires access to unread audio after stopping, but ALSA's
-     * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's
-     * available now so it'll be available later after the drop. */
-    avail = ALCcaptureAlsa_availableSamples(self);
-    if(!self->ring && avail > 0)
-    {
-        /* The ring buffer implicitly captures when checking availability.
-         * Direct access needs to explicitly capture it into temp storage. */
-        ALsizei size;
-        void *ptr;
-
-        size = snd_pcm_frames_to_bytes(self->pcmHandle, avail);
-        ptr = al_malloc(16, size);
-        if(ptr)
-        {
-            ALCcaptureAlsa_captureSamples(self, ptr, avail);
-            al_free(self->buffer);
-            self->buffer = ptr;
-            self->size = size;
-        }
-    }
-    err = snd_pcm_drop(self->pcmHandle);
-    if(err < 0)
-        ERR("drop failed: %s\n", snd_strerror(err));
-    self->doCapture = AL_FALSE;
-}
-
-static ALCenum ALCcaptureAlsa_captureSamples(ALCcaptureAlsa *self, ALCvoid *buffer, ALCuint samples)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    if(self->ring)
-    {
-        ll_ringbuffer_read(self->ring, buffer, samples);
-        return ALC_NO_ERROR;
-    }
-
-    self->last_avail -= samples;
-    while(ATOMIC_LOAD(&device->Connected, almemory_order_acquire) && samples > 0)
-    {
-        snd_pcm_sframes_t amt = 0;
-
-        if(self->size > 0)
-        {
-            /* First get any data stored from the last stop */
-            amt = snd_pcm_bytes_to_frames(self->pcmHandle, self->size);
-            if((snd_pcm_uframes_t)amt > samples) amt = samples;
-
-            amt = snd_pcm_frames_to_bytes(self->pcmHandle, amt);
-            memcpy(buffer, self->buffer, amt);
-
-            if(self->size > amt)
-            {
-                memmove(self->buffer, self->buffer+amt, self->size - amt);
-                self->size -= amt;
-            }
-            else
-            {
-                al_free(self->buffer);
-                self->buffer = NULL;
-                self->size = 0;
-            }
-            amt = snd_pcm_bytes_to_frames(self->pcmHandle, amt);
-        }
-        else if(self->doCapture)
-            amt = snd_pcm_readi(self->pcmHandle, buffer, samples);
-        if(amt < 0)
-        {
-            ERR("read error: %s\n", snd_strerror(amt));
-
-            if(amt == -EAGAIN)
-                continue;
-            if((amt=snd_pcm_recover(self->pcmHandle, amt, 1)) >= 0)
-            {
-                amt = snd_pcm_start(self->pcmHandle);
-                if(amt >= 0)
-                    amt = snd_pcm_avail_update(self->pcmHandle);
-            }
-            if(amt < 0)
-            {
-                ERR("restore error: %s\n", snd_strerror(amt));
-                aluHandleDisconnect(device, "Capture recovery failure: %s", snd_strerror(amt));
-                break;
-            }
-            /* If the amount available is less than what's asked, we lost it
-             * during recovery. So just give silence instead. */
-            if((snd_pcm_uframes_t)amt < samples)
-                break;
-            continue;
-        }
-
-        buffer = (ALbyte*)buffer + amt;
-        samples -= amt;
-    }
-    if(samples > 0)
-        memset(buffer, ((device->FmtType == DevFmtUByte) ? 0x80 : 0),
-               snd_pcm_frames_to_bytes(self->pcmHandle, samples));
-
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCcaptureAlsa_availableSamples(ALCcaptureAlsa *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    snd_pcm_sframes_t avail = 0;
-
-    if(ATOMIC_LOAD(&device->Connected, almemory_order_acquire) && self->doCapture)
-        avail = snd_pcm_avail_update(self->pcmHandle);
-    if(avail < 0)
-    {
-        ERR("avail update failed: %s\n", snd_strerror(avail));
-
-        if((avail=snd_pcm_recover(self->pcmHandle, avail, 1)) >= 0)
-        {
-            if(self->doCapture)
-                avail = snd_pcm_start(self->pcmHandle);
-            if(avail >= 0)
-                avail = snd_pcm_avail_update(self->pcmHandle);
-        }
-        if(avail < 0)
-        {
-            ERR("restore error: %s\n", snd_strerror(avail));
-            aluHandleDisconnect(device, "Capture recovery failure: %s", snd_strerror(avail));
-        }
-    }
-
-    if(!self->ring)
-    {
-        if(avail < 0) avail = 0;
-        avail += snd_pcm_bytes_to_frames(self->pcmHandle, self->size);
-        if(avail > self->last_avail) self->last_avail = avail;
-        return self->last_avail;
-    }
-
-    while(avail > 0)
-    {
-        ll_ringbuffer_data_t vec[2];
-        snd_pcm_sframes_t amt;
-
-        ll_ringbuffer_get_write_vector(self->ring, vec);
-        if(vec[0].len == 0) break;
-
-        amt = (vec[0].len < (snd_pcm_uframes_t)avail) ?
-              vec[0].len : (snd_pcm_uframes_t)avail;
-        amt = snd_pcm_readi(self->pcmHandle, vec[0].buf, amt);
-        if(amt < 0)
-        {
-            ERR("read error: %s\n", snd_strerror(amt));
-
-            if(amt == -EAGAIN)
-                continue;
-            if((amt=snd_pcm_recover(self->pcmHandle, amt, 1)) >= 0)
-            {
-                if(self->doCapture)
-                    amt = snd_pcm_start(self->pcmHandle);
-                if(amt >= 0)
-                    amt = snd_pcm_avail_update(self->pcmHandle);
-            }
-            if(amt < 0)
-            {
-                ERR("restore error: %s\n", snd_strerror(amt));
-                aluHandleDisconnect(device, "Capture recovery failure: %s", snd_strerror(amt));
-                break;
-            }
-            avail = amt;
-            continue;
-        }
-
-        ll_ringbuffer_write_advance(self->ring, amt);
-        avail -= amt;
-    }
-
-    return ll_ringbuffer_read_space(self->ring);
-}
-
-static ClockLatency ALCcaptureAlsa_getClockLatency(ALCcaptureAlsa *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    snd_pcm_sframes_t delay = 0;
-    ClockLatency ret;
-    int err;
-
-    ALCcaptureAlsa_lock(self);
-    ret.ClockTime = GetDeviceClockTime(device);
-    if((err=snd_pcm_delay(self->pcmHandle, &delay)) < 0)
-    {
-        ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
-        delay = 0;
-    }
-    if(delay < 0) delay = 0;
-    ret.Latency = delay * DEVICE_CLOCK_RES / device->Frequency;
-    ALCcaptureAlsa_unlock(self);
-
-    return ret;
-}
-
-
-static inline void AppendAllDevicesList2(const DevMap *entry)
-{ AppendAllDevicesList(alstr_get_cstr(entry->name)); }
-static inline void AppendCaptureDeviceList2(const DevMap *entry)
-{ AppendCaptureDeviceList(alstr_get_cstr(entry->name)); }
-
-typedef struct ALCalsaBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCalsaBackendFactory;
-#define ALCALSABACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCalsaBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCalsaBackendFactory_init(ALCalsaBackendFactory* UNUSED(self))
-{
-    VECTOR_INIT(PlaybackDevices);
-    VECTOR_INIT(CaptureDevices);
-
-    if(!alsa_load())
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCalsaBackendFactory_deinit(ALCalsaBackendFactory* UNUSED(self))
-{
-    clear_devlist(&PlaybackDevices);
-    VECTOR_DEINIT(PlaybackDevices);
-
-    clear_devlist(&CaptureDevices);
-    VECTOR_DEINIT(CaptureDevices);
-
-#ifdef HAVE_DYNLOAD
-    if(alsa_handle)
-        CloseLib(alsa_handle);
-    alsa_handle = NULL;
-#endif
-}
-
-static ALCboolean ALCalsaBackendFactory_querySupport(ALCalsaBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCalsaBackendFactory_probe(ALCalsaBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            probe_devices(SND_PCM_STREAM_PLAYBACK, &PlaybackDevices);
-            VECTOR_FOR_EACH(const DevMap, PlaybackDevices, AppendAllDevicesList2);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            probe_devices(SND_PCM_STREAM_CAPTURE, &CaptureDevices);
-            VECTOR_FOR_EACH(const DevMap, CaptureDevices, AppendCaptureDeviceList2);
-            break;
-    }
-}
-
-static ALCbackend* ALCalsaBackendFactory_createBackend(ALCalsaBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCplaybackAlsa *backend;
-        NEW_OBJ(backend, ALCplaybackAlsa)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCcaptureAlsa *backend;
-        NEW_OBJ(backend, ALCcaptureAlsa)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCalsaBackendFactory);
-
-
-ALCbackendFactory *ALCalsaBackendFactory_getFactory(void)
-{
-    static ALCalsaBackendFactory factory = ALCALSABACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 1268 - 0
Engine/lib/openal-soft/Alc/backends/alsa.cpp

@@ -0,0 +1,1268 @@
+/**
+ * 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 "backends/alsa.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cassert>
+#include <cerrno>
+#include <chrono>
+#include <cstring>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "albyte.h"
+#include "alcmain.h"
+#include "alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alu.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "vector.h"
+
+#include <alsa/asoundlib.h>
+
+
+namespace {
+
+constexpr char alsaDevice[] = "ALSA Default";
+
+
+#ifdef HAVE_DYNLOAD
+#define ALSA_FUNCS(MAGIC)                                                     \
+    MAGIC(snd_strerror);                                                      \
+    MAGIC(snd_pcm_open);                                                      \
+    MAGIC(snd_pcm_close);                                                     \
+    MAGIC(snd_pcm_nonblock);                                                  \
+    MAGIC(snd_pcm_frames_to_bytes);                                           \
+    MAGIC(snd_pcm_bytes_to_frames);                                           \
+    MAGIC(snd_pcm_hw_params_malloc);                                          \
+    MAGIC(snd_pcm_hw_params_free);                                            \
+    MAGIC(snd_pcm_hw_params_any);                                             \
+    MAGIC(snd_pcm_hw_params_current);                                         \
+    MAGIC(snd_pcm_hw_params_set_access);                                      \
+    MAGIC(snd_pcm_hw_params_set_format);                                      \
+    MAGIC(snd_pcm_hw_params_set_channels);                                    \
+    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_current);                                         \
+    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);                                                    \
+    MAGIC(snd_pcm_reset);                                                     \
+    MAGIC(snd_pcm_wait);                                                      \
+    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);                                                     \
+    MAGIC(snd_pcm_writei);                                                    \
+    MAGIC(snd_pcm_drain);                                                     \
+    MAGIC(snd_pcm_drop);                                                      \
+    MAGIC(snd_pcm_recover);                                                   \
+    MAGIC(snd_pcm_info_malloc);                                               \
+    MAGIC(snd_pcm_info_free);                                                 \
+    MAGIC(snd_pcm_info_set_device);                                           \
+    MAGIC(snd_pcm_info_set_subdevice);                                        \
+    MAGIC(snd_pcm_info_set_stream);                                           \
+    MAGIC(snd_pcm_info_get_name);                                             \
+    MAGIC(snd_ctl_pcm_next_device);                                           \
+    MAGIC(snd_ctl_pcm_info);                                                  \
+    MAGIC(snd_ctl_open);                                                      \
+    MAGIC(snd_ctl_close);                                                     \
+    MAGIC(snd_ctl_card_info_malloc);                                          \
+    MAGIC(snd_ctl_card_info_free);                                            \
+    MAGIC(snd_ctl_card_info);                                                 \
+    MAGIC(snd_ctl_card_info_get_name);                                        \
+    MAGIC(snd_ctl_card_info_get_id);                                          \
+    MAGIC(snd_card_next);                                                     \
+    MAGIC(snd_config_update_free_global)
+
+static void *alsa_handle;
+#define MAKE_FUNC(f) decltype(f) * p##f
+ALSA_FUNCS(MAKE_FUNC);
+#undef MAKE_FUNC
+
+#ifndef IN_IDE_PARSER
+#define snd_strerror psnd_strerror
+#define snd_pcm_open psnd_pcm_open
+#define snd_pcm_close psnd_pcm_close
+#define snd_pcm_nonblock psnd_pcm_nonblock
+#define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes
+#define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames
+#define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc
+#define snd_pcm_hw_params_free psnd_pcm_hw_params_free
+#define snd_pcm_hw_params_any psnd_pcm_hw_params_any
+#define snd_pcm_hw_params_current psnd_pcm_hw_params_current
+#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access
+#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format
+#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels
+#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near
+#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near
+#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate
+#define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample
+#define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near
+#define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near
+#define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near
+#define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near
+#define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min
+#define snd_pcm_hw_params_get_buffer_time_min psnd_pcm_hw_params_get_buffer_time_min
+#define snd_pcm_hw_params_get_buffer_time_max psnd_pcm_hw_params_get_buffer_time_max
+#define snd_pcm_hw_params_get_period_time_min psnd_pcm_hw_params_get_period_time_min
+#define snd_pcm_hw_params_get_period_time_max psnd_pcm_hw_params_get_period_time_max
+#define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size
+#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size
+#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access
+#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods
+#define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format
+#define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels
+#define snd_pcm_hw_params psnd_pcm_hw_params
+#define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc
+#define snd_pcm_sw_params_current psnd_pcm_sw_params_current
+#define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min
+#define snd_pcm_sw_params_set_stop_threshold psnd_pcm_sw_params_set_stop_threshold
+#define snd_pcm_sw_params psnd_pcm_sw_params
+#define snd_pcm_sw_params_free psnd_pcm_sw_params_free
+#define snd_pcm_prepare psnd_pcm_prepare
+#define snd_pcm_start psnd_pcm_start
+#define snd_pcm_resume psnd_pcm_resume
+#define snd_pcm_reset psnd_pcm_reset
+#define snd_pcm_wait psnd_pcm_wait
+#define snd_pcm_delay psnd_pcm_delay
+#define snd_pcm_state psnd_pcm_state
+#define snd_pcm_avail_update psnd_pcm_avail_update
+#define snd_pcm_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
+#define snd_pcm_writei psnd_pcm_writei
+#define snd_pcm_drain psnd_pcm_drain
+#define snd_pcm_drop psnd_pcm_drop
+#define snd_pcm_recover psnd_pcm_recover
+#define snd_pcm_info_malloc psnd_pcm_info_malloc
+#define snd_pcm_info_free psnd_pcm_info_free
+#define snd_pcm_info_set_device psnd_pcm_info_set_device
+#define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice
+#define snd_pcm_info_set_stream psnd_pcm_info_set_stream
+#define snd_pcm_info_get_name psnd_pcm_info_get_name
+#define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device
+#define snd_ctl_pcm_info psnd_ctl_pcm_info
+#define snd_ctl_open psnd_ctl_open
+#define snd_ctl_close psnd_ctl_close
+#define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc
+#define snd_ctl_card_info_free psnd_ctl_card_info_free
+#define snd_ctl_card_info psnd_ctl_card_info
+#define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name
+#define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id
+#define snd_card_next psnd_card_next
+#define snd_config_update_free_global psnd_config_update_free_global
+#endif
+#endif
+
+
+struct HwParamsDeleter {
+    void operator()(snd_pcm_hw_params_t *ptr) { snd_pcm_hw_params_free(ptr); }
+};
+using HwParamsPtr = std::unique_ptr<snd_pcm_hw_params_t,HwParamsDeleter>;
+HwParamsPtr CreateHwParams()
+{
+    snd_pcm_hw_params_t *hp{};
+    snd_pcm_hw_params_malloc(&hp);
+    return HwParamsPtr{hp};
+}
+
+struct SwParamsDeleter {
+    void operator()(snd_pcm_sw_params_t *ptr) { snd_pcm_sw_params_free(ptr); }
+};
+using SwParamsPtr = std::unique_ptr<snd_pcm_sw_params_t,SwParamsDeleter>;
+SwParamsPtr CreateSwParams()
+{
+    snd_pcm_sw_params_t *sp{};
+    snd_pcm_sw_params_malloc(&sp);
+    return SwParamsPtr{sp};
+}
+
+
+struct DevMap {
+    std::string name;
+    std::string device_name;
+};
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+const char *prefix_name(snd_pcm_stream_t stream)
+{
+    assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE);
+    return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix";
+}
+
+al::vector<DevMap> probe_devices(snd_pcm_stream_t stream)
+{
+    al::vector<DevMap> devlist;
+
+    snd_ctl_card_info_t *info;
+    snd_ctl_card_info_malloc(&info);
+    snd_pcm_info_t *pcminfo;
+    snd_pcm_info_malloc(&pcminfo);
+
+    devlist.emplace_back(DevMap{alsaDevice,
+        GetConfigValue(nullptr, "alsa", (stream==SND_PCM_STREAM_PLAYBACK) ? "device" : "capture",
+            "default")});
+
+    const char *customdevs{GetConfigValue(nullptr, "alsa",
+        (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures", "")};
+    while(const char *curdev{customdevs})
+    {
+        if(!curdev[0]) break;
+        customdevs = strchr(curdev, ';');
+        const char *sep{strchr(curdev, '=')};
+        if(!sep)
+        {
+            std::string spec{customdevs ? std::string(curdev, customdevs++) : std::string(curdev)};
+            ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str());
+            continue;
+        }
+
+        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());
+    }
+
+    const std::string main_prefix{
+        ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")};
+
+    int card{-1};
+    int err{snd_card_next(&card)};
+    for(;err >= 0 && card >= 0;err = snd_card_next(&card))
+    {
+        std::string name{"hw:" + std::to_string(card)};
+
+        snd_ctl_t *handle;
+        if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0)
+        {
+            ERR("control open (hw:%d): %s\n", card, snd_strerror(err));
+            continue;
+        }
+        if((err=snd_ctl_card_info(handle, info)) < 0)
+        {
+            ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err));
+            snd_ctl_close(handle);
+            continue;
+        }
+
+        const char *cardname{snd_ctl_card_info_get_name(info)};
+        const char *cardid{snd_ctl_card_info_get_id(info)};
+        name = prefix_name(stream);
+        name += '-';
+        name += cardid;
+        const std::string card_prefix{
+            ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)};
+
+        int dev{-1};
+        while(1)
+        {
+            if(snd_ctl_pcm_next_device(handle, &dev) < 0)
+                ERR("snd_ctl_pcm_next_device failed\n");
+            if(dev < 0) break;
+
+            snd_pcm_info_set_device(pcminfo, static_cast<uint>(dev));
+            snd_pcm_info_set_subdevice(pcminfo, 0);
+            snd_pcm_info_set_stream(pcminfo, stream);
+            if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0)
+            {
+                if(err != -ENOENT)
+                    ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err));
+                continue;
+            }
+
+            /* "prefix-cardid-dev" */
+            name = prefix_name(stream);
+            name += '-';
+            name += cardid;
+            name += '-';
+            name += std::to_string(dev);
+            const std::string device_prefix{
+                ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)};
+
+            /* "CardName, PcmName (CARD=cardid,DEV=dev)" */
+            name = cardname;
+            name += ", ";
+            name += snd_pcm_info_get_name(pcminfo);
+            name += " (CARD=";
+            name += cardid;
+            name += ",DEV=";
+            name += std::to_string(dev);
+            name += ')';
+
+            /* "devprefixCARD=cardid,DEV=dev" */
+            std::string device{device_prefix};
+            device += "CARD=";
+            device += cardid;
+            device += ",DEV=";
+            device += std::to_string(dev);
+            
+            devlist.emplace_back(DevMap{std::move(name), std::move(device)});
+            const auto &entry = devlist.back();
+            TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
+        }
+        snd_ctl_close(handle);
+    }
+    if(err < 0)
+        ERR("snd_card_next failed: %s\n", snd_strerror(err));
+
+    snd_pcm_info_free(pcminfo);
+    snd_ctl_card_info_free(info);
+
+    return devlist;
+}
+
+
+int verify_state(snd_pcm_t *handle)
+{
+    snd_pcm_state_t state{snd_pcm_state(handle)};
+
+    int err;
+    switch(state)
+    {
+        case SND_PCM_STATE_OPEN:
+        case SND_PCM_STATE_SETUP:
+        case SND_PCM_STATE_PREPARED:
+        case SND_PCM_STATE_RUNNING:
+        case SND_PCM_STATE_DRAINING:
+        case SND_PCM_STATE_PAUSED:
+            /* All Okay */
+            break;
+
+        case SND_PCM_STATE_XRUN:
+            if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0)
+                return err;
+            break;
+        case SND_PCM_STATE_SUSPENDED:
+            if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0)
+                return err;
+            break;
+        case SND_PCM_STATE_DISCONNECTED:
+            return -ENODEV;
+    }
+
+    return state;
+}
+
+
+struct AlsaPlayback final : public BackendBase {
+    AlsaPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~AlsaPlayback() override;
+
+    int mixerProc();
+    int mixerNoMMapProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    ClockLatency getClockLatency() override;
+
+    snd_pcm_t *mPcmHandle{nullptr};
+
+    std::mutex mMutex;
+
+    al::vector<al::byte> mBuffer;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(AlsaPlayback)
+};
+
+AlsaPlayback::~AlsaPlayback()
+{
+    if(mPcmHandle)
+        snd_pcm_close(mPcmHandle);
+    mPcmHandle = nullptr;
+}
+
+
+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))
+    {
+        int state{verify_state(mPcmHandle)};
+        if(state < 0)
+        {
+            ERR("Invalid state detected: %s\n", snd_strerror(state));
+            mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
+            break;
+        }
+
+        snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)};
+        if(avails < 0)
+        {
+            ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avails)));
+            continue;
+        }
+        snd_pcm_uframes_t avail{static_cast<snd_pcm_uframes_t>(avails)};
+
+        if(avail > buffer_size)
+        {
+            WARN("available samples exceeds the buffer size\n");
+            snd_pcm_reset(mPcmHandle);
+            continue;
+        }
+
+        // make sure there's frames to process
+        if(avail < update_size)
+        {
+            if(state != SND_PCM_STATE_RUNNING)
+            {
+                int err{snd_pcm_start(mPcmHandle)};
+                if(err < 0)
+                {
+                    ERR("start failed: %s\n", snd_strerror(err));
+                    continue;
+                }
+            }
+            if(snd_pcm_wait(mPcmHandle, 1000) == 0)
+                ERR("Wait timeout... buffer size too low?\n");
+            continue;
+        }
+        avail -= avail%update_size;
+
+        // it is possible that contiguous areas are smaller, thus we use a loop
+        std::lock_guard<std::mutex> _{mMutex};
+        while(avail > 0)
+        {
+            snd_pcm_uframes_t frames{avail};
+
+            const snd_pcm_channel_area_t *areas{};
+            snd_pcm_uframes_t offset{};
+            int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)};
+            if(err < 0)
+            {
+                ERR("mmap begin error: %s\n", snd_strerror(err));
+                break;
+            }
+
+            char *WritePtr{static_cast<char*>(areas->addr) + (offset * areas->step / 8)};
+            mDevice->renderSamples(WritePtr, static_cast<uint>(frames), areas->step/samplebits);
+
+            snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
+            if(commitres < 0 || static_cast<snd_pcm_uframes_t>(commitres) != frames)
+            {
+                ERR("mmap commit error: %s\n",
+                    snd_strerror(commitres >= 0 ? -EPIPE : static_cast<int>(commitres)));
+                break;
+            }
+
+            avail -= frames;
+        }
+    }
+
+    return 0;
+}
+
+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))
+    {
+        int state{verify_state(mPcmHandle)};
+        if(state < 0)
+        {
+            ERR("Invalid state detected: %s\n", snd_strerror(state));
+            mDevice->handleDisconnect("Bad state: %s", snd_strerror(state));
+            break;
+        }
+
+        snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)};
+        if(avail < 0)
+        {
+            ERR("available update failed: %s\n", snd_strerror(static_cast<int>(avail)));
+            continue;
+        }
+
+        if(static_cast<snd_pcm_uframes_t>(avail) > buffer_size)
+        {
+            WARN("available samples exceeds the buffer size\n");
+            snd_pcm_reset(mPcmHandle);
+            continue;
+        }
+
+        if(static_cast<snd_pcm_uframes_t>(avail) < update_size)
+        {
+            if(state != SND_PCM_STATE_RUNNING)
+            {
+                int err{snd_pcm_start(mPcmHandle)};
+                if(err < 0)
+                {
+                    ERR("start failed: %s\n", snd_strerror(err));
+                    continue;
+                }
+            }
+            if(snd_pcm_wait(mPcmHandle, 1000) == 0)
+                ERR("Wait timeout... buffer size too low?\n");
+            continue;
+        }
+
+        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);
+        while(avail > 0)
+        {
+            snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr,
+                static_cast<snd_pcm_uframes_t>(avail))};
+            switch(ret)
+            {
+            case -EAGAIN:
+                continue;
+#if ESTRPIPE != EPIPE
+            case -ESTRPIPE:
+#endif
+            case -EPIPE:
+            case -EINTR:
+                ret = snd_pcm_recover(mPcmHandle, static_cast<int>(ret), 1);
+                if(ret < 0)
+                    avail = 0;
+                break;
+            default:
+                if(ret >= 0)
+                {
+                    WritePtr += snd_pcm_frames_to_bytes(mPcmHandle, ret);
+                    avail -= ret;
+                }
+                break;
+            }
+            if(ret < 0)
+            {
+                ret = snd_pcm_prepare(mPcmHandle);
+                if(ret < 0) break;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+void AlsaPlayback::open(const char *name)
+{
+    const char *driver{};
+    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; }
+        );
+        if(iter == PlaybackDevices.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
+        driver = iter->device_name.c_str();
+    }
+    else
+    {
+        name = alsaDevice;
+        driver = GetConfigValue(nullptr, "alsa", "device", "default");
+    }
+
+    TRACE("Opening device \"%s\"\n", driver);
+    int err{snd_pcm_open(&mPcmHandle, 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};
+
+    /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
+    snd_config_update_free_global();
+
+    mDevice->DeviceName = name;
+}
+
+bool AlsaPlayback::reset()
+{
+    snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN};
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        format = SND_PCM_FORMAT_S8;
+        break;
+    case DevFmtUByte:
+        format = SND_PCM_FORMAT_U8;
+        break;
+    case DevFmtShort:
+        format = SND_PCM_FORMAT_S16;
+        break;
+    case DevFmtUShort:
+        format = SND_PCM_FORMAT_U16;
+        break;
+    case DevFmtInt:
+        format = SND_PCM_FORMAT_S32;
+        break;
+    case DevFmtUInt:
+        format = SND_PCM_FORMAT_U32;
+        break;
+    case DevFmtFloat:
+        format = SND_PCM_FORMAT_FLOAT;
+        break;
+    }
+
+    bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)};
+    uint periodLen{static_cast<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
+    uint bufferLen{static_cast<uint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
+    uint rate{mDevice->Frequency};
+
+    int err{};
+    HwParamsPtr hp{CreateHwParams()};
+#define CHECK(x) do {                                                         \
+    if((err=(x)) < 0)                                                         \
+        throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+            snd_strerror(err)};                                               \
+} while(0)
+    CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
+    /* set interleaved access */
+    if(!allowmmap
+        || snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0)
+    {
+        /* No mmap */
+        CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED));
+    }
+    /* test and set format (implicitly sets sample bits) */
+    if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0)
+    {
+        static const struct {
+            snd_pcm_format_t format;
+            DevFmtType fmttype;
+        } formatlist[] = {
+            { SND_PCM_FORMAT_FLOAT, DevFmtFloat  },
+            { SND_PCM_FORMAT_S32,   DevFmtInt    },
+            { SND_PCM_FORMAT_U32,   DevFmtUInt   },
+            { SND_PCM_FORMAT_S16,   DevFmtShort  },
+            { SND_PCM_FORMAT_U16,   DevFmtUShort },
+            { SND_PCM_FORMAT_S8,    DevFmtByte   },
+            { SND_PCM_FORMAT_U8,    DevFmtUByte  },
+        };
+
+        for(const auto &fmt : formatlist)
+        {
+            format = fmt.format;
+            if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) >= 0)
+            {
+                mDevice->FmtType = fmt.fmttype;
+                break;
+            }
+        }
+    }
+    CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
+    /* test and set channels (implicitly sets frame bits) */
+    if(snd_pcm_hw_params_test_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;
+            }
+        }
+    }
+    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");
+    }
+    else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0)
+        ERR("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)
+        ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err));
+    /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */
+    if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0)
+        ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err));
+    /* install and prepare hardware configuration */
+    CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
+
+    /* retrieve configuration info */
+    snd_pcm_uframes_t periodSizeInFrames{};
+    snd_pcm_uframes_t bufferSizeInFrames{};
+    snd_pcm_access_t access{};
+
+    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));
+    hp = nullptr;
+
+    SwParamsPtr sp{CreateSwParams()};
+    CHECK(snd_pcm_sw_params_current(mPcmHandle, sp.get()));
+    CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp.get(), periodSizeInFrames));
+    CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp.get(), bufferSizeInFrames));
+    CHECK(snd_pcm_sw_params(mPcmHandle, sp.get()));
+#undef CHECK
+    sp = nullptr;
+
+    mDevice->BufferSize = static_cast<uint>(bufferSizeInFrames);
+    mDevice->UpdateSize = static_cast<uint>(periodSizeInFrames);
+    mDevice->Frequency = rate;
+
+    setDefaultChannelOrder();
+
+    return true;
+}
+
+void AlsaPlayback::start()
+{
+    int err{};
+    snd_pcm_access_t access{};
+    HwParamsPtr hp{CreateHwParams()};
+#define CHECK(x) do {                                                         \
+    if((err=(x)) < 0)                                                         \
+        throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+            snd_strerror(err)};                                               \
+} while(0)
+    CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get()));
+    /* retrieve configuration info */
+    CHECK(snd_pcm_hw_params_get_access(hp.get(), &access));
+    hp = nullptr;
+
+    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)));
+        thread_func = &AlsaPlayback::mixerNoMMapProc;
+    }
+    else
+    {
+        CHECK(snd_pcm_prepare(mPcmHandle));
+        thread_func = &AlsaPlayback::mixerProc;
+    }
+#undef CHECK
+
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(thread_func), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void AlsaPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    mBuffer.clear();
+    int err{snd_pcm_drop(mPcmHandle)};
+    if(err < 0)
+        ERR("snd_pcm_drop failed: %s\n", snd_strerror(err));
+}
+
+ClockLatency AlsaPlayback::getClockLatency()
+{
+    ClockLatency ret;
+
+    std::lock_guard<std::mutex> _{mMutex};
+    ret.ClockTime = GetDeviceClockTime(mDevice);
+    snd_pcm_sframes_t delay{};
+    int err{snd_pcm_delay(mPcmHandle, &delay)};
+    if(err < 0)
+    {
+        ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
+        delay = 0;
+    }
+    ret.Latency  = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+
+struct AlsaCapture final : public BackendBase {
+    AlsaCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~AlsaCapture() override;
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+    ClockLatency getClockLatency() override;
+
+    snd_pcm_t *mPcmHandle{nullptr};
+
+    al::vector<al::byte> mBuffer;
+
+    bool mDoCapture{false};
+    RingBufferPtr mRing{nullptr};
+
+    snd_pcm_sframes_t mLastAvail{0};
+
+    DEF_NEWDEL(AlsaCapture)
+};
+
+AlsaCapture::~AlsaCapture()
+{
+    if(mPcmHandle)
+        snd_pcm_close(mPcmHandle);
+    mPcmHandle = nullptr;
+}
+
+
+void AlsaCapture::open(const char *name)
+{
+    const char *driver{};
+    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; }
+        );
+        if(iter == CaptureDevices.cend())
+            throw al::backend_exception{al::backend_error::NoDevice,
+                "Device name \"%s\" not found", name};
+        driver = iter->device_name.c_str();
+    }
+    else
+    {
+        name = alsaDevice;
+        driver = GetConfigValue(nullptr, "alsa", "capture", "default");
+    }
+
+    TRACE("Opening device \"%s\"\n", driver);
+    int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)};
+    if(err < 0)
+        throw al::backend_exception{al::backend_error::NoDevice,
+            "Could not open ALSA device \"%s\"", driver};
+
+    /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */
+    snd_config_update_free_global();
+
+    snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN};
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        format = SND_PCM_FORMAT_S8;
+        break;
+    case DevFmtUByte:
+        format = SND_PCM_FORMAT_U8;
+        break;
+    case DevFmtShort:
+        format = SND_PCM_FORMAT_S16;
+        break;
+    case DevFmtUShort:
+        format = SND_PCM_FORMAT_U16;
+        break;
+    case DevFmtInt:
+        format = SND_PCM_FORMAT_S32;
+        break;
+    case DevFmtUInt:
+        format = SND_PCM_FORMAT_U32;
+        break;
+    case DevFmtFloat:
+        format = SND_PCM_FORMAT_FLOAT;
+        break;
+    }
+
+    snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)};
+    snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)};
+
+    bool needring{false};
+    HwParamsPtr hp{CreateHwParams()};
+#define CHECK(x) do {                                                         \
+    if((err=(x)) < 0)                                                         \
+        throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \
+            snd_strerror(err)};                                               \
+} while(0)
+    CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get()));
+    /* set interleaved access */
+    CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED));
+    /* set format (implicitly sets sample bits) */
+    CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format));
+    /* set channels (implicitly sets frame bits) */
+    CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()));
+    /* set rate (implicitly constrains period/buffer parameters) */
+    CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->Frequency, 0));
+    /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
+    if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0)
+    {
+        TRACE("Buffer too large, using intermediate ring buffer\n");
+        needring = true;
+        CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames));
+    }
+    /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */
+    CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp.get(), &periodSizeInFrames, nullptr));
+    /* install and prepare hardware configuration */
+    CHECK(snd_pcm_hw_params(mPcmHandle, hp.get()));
+    /* retrieve configuration info */
+    CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr));
+#undef CHECK
+    hp = nullptr;
+
+    if(needring)
+        mRing = RingBuffer::Create(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false);
+
+    mDevice->DeviceName = name;
+}
+
+
+void AlsaCapture::start()
+{
+    int err{snd_pcm_prepare(mPcmHandle)};
+    if(err < 0)
+        throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s",
+            snd_strerror(err)};
+
+    err = snd_pcm_start(mPcmHandle);
+    if(err < 0)
+        throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s",
+            snd_strerror(err)};
+
+    mDoCapture = true;
+}
+
+void AlsaCapture::stop()
+{
+    /* OpenAL requires access to unread audio after stopping, but ALSA's
+     * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's
+     * available now so it'll be available later after the drop.
+     */
+    uint avail{availableSamples()};
+    if(!mRing && avail > 0)
+    {
+        /* The ring buffer implicitly captures when checking availability.
+         * Direct access needs to explicitly capture it into temp storage.
+         */
+        auto temp = al::vector<al::byte>(
+            static_cast<size_t>(snd_pcm_frames_to_bytes(mPcmHandle, avail)));
+        captureSamples(temp.data(), avail);
+        mBuffer = std::move(temp);
+    }
+    int err{snd_pcm_drop(mPcmHandle)};
+    if(err < 0)
+        ERR("drop failed: %s\n", snd_strerror(err));
+    mDoCapture = false;
+}
+
+void AlsaCapture::captureSamples(al::byte *buffer, uint samples)
+{
+    if(mRing)
+    {
+        mRing->read(buffer, samples);
+        return;
+    }
+
+    mLastAvail -= samples;
+    while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0)
+    {
+        snd_pcm_sframes_t amt{0};
+
+        if(!mBuffer.empty())
+        {
+            /* First get any data stored from the last stop */
+            amt = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
+            if(static_cast<snd_pcm_uframes_t>(amt) > samples) amt = samples;
+
+            amt = snd_pcm_frames_to_bytes(mPcmHandle, amt);
+            std::copy_n(mBuffer.begin(), amt, buffer);
+
+            mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt);
+            amt = snd_pcm_bytes_to_frames(mPcmHandle, amt);
+        }
+        else if(mDoCapture)
+            amt = snd_pcm_readi(mPcmHandle, buffer, samples);
+        if(amt < 0)
+        {
+            ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
+
+            if(amt == -EAGAIN)
+                continue;
+            if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
+            {
+                amt = snd_pcm_start(mPcmHandle);
+                if(amt >= 0)
+                    amt = snd_pcm_avail_update(mPcmHandle);
+            }
+            if(amt < 0)
+            {
+                const char *err{snd_strerror(static_cast<int>(amt))};
+                ERR("restore error: %s\n", err);
+                mDevice->handleDisconnect("Capture recovery failure: %s", err);
+                break;
+            }
+            /* If the amount available is less than what's asked, we lost it
+             * during recovery. So just give silence instead. */
+            if(static_cast<snd_pcm_uframes_t>(amt) < samples)
+                break;
+            continue;
+        }
+
+        buffer = buffer + amt;
+        samples -= static_cast<uint>(amt);
+    }
+    if(samples > 0)
+        std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
+            al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
+}
+
+uint AlsaCapture::availableSamples()
+{
+    snd_pcm_sframes_t avail{0};
+    if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture)
+        avail = snd_pcm_avail_update(mPcmHandle);
+    if(avail < 0)
+    {
+        ERR("avail update failed: %s\n", snd_strerror(static_cast<int>(avail)));
+
+        if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
+        {
+            if(mDoCapture)
+                avail = snd_pcm_start(mPcmHandle);
+            if(avail >= 0)
+                avail = snd_pcm_avail_update(mPcmHandle);
+        }
+        if(avail < 0)
+        {
+            const char *err{snd_strerror(static_cast<int>(avail))};
+            ERR("restore error: %s\n", err);
+            mDevice->handleDisconnect("Capture recovery failure: %s", err);
+        }
+    }
+
+    if(!mRing)
+    {
+        if(avail < 0) avail = 0;
+        avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
+        if(avail > mLastAvail) mLastAvail = avail;
+        return static_cast<uint>(mLastAvail);
+    }
+
+    while(avail > 0)
+    {
+        auto vec = mRing->getWriteVector();
+        if(vec.first.len == 0) break;
+
+        snd_pcm_sframes_t amt{std::min(static_cast<snd_pcm_sframes_t>(vec.first.len), avail)};
+        amt = snd_pcm_readi(mPcmHandle, vec.first.buf, static_cast<snd_pcm_uframes_t>(amt));
+        if(amt < 0)
+        {
+            ERR("read error: %s\n", snd_strerror(static_cast<int>(amt)));
+
+            if(amt == -EAGAIN)
+                continue;
+            if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
+            {
+                if(mDoCapture)
+                    amt = snd_pcm_start(mPcmHandle);
+                if(amt >= 0)
+                    amt = snd_pcm_avail_update(mPcmHandle);
+            }
+            if(amt < 0)
+            {
+                const char *err{snd_strerror(static_cast<int>(amt))};
+                ERR("restore error: %s\n", err);
+                mDevice->handleDisconnect("Capture recovery failure: %s", err);
+                break;
+            }
+            avail = amt;
+            continue;
+        }
+
+        mRing->writeAdvance(static_cast<snd_pcm_uframes_t>(amt));
+        avail -= amt;
+    }
+
+    return static_cast<uint>(mRing->readSpace());
+}
+
+ClockLatency AlsaCapture::getClockLatency()
+{
+    ClockLatency ret;
+
+    ret.ClockTime = GetDeviceClockTime(mDevice);
+    snd_pcm_sframes_t delay{};
+    int err{snd_pcm_delay(mPcmHandle, &delay)};
+    if(err < 0)
+    {
+        ERR("Failed to get pcm delay: %s\n", snd_strerror(err));
+        delay = 0;
+    }
+    ret.Latency  = std::chrono::seconds{std::max<snd_pcm_sframes_t>(0, delay)};
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+} // namespace
+
+
+bool AlsaBackendFactory::init()
+{
+    bool error{false};
+
+#ifdef HAVE_DYNLOAD
+    if(!alsa_handle)
+    {
+        std::string missing_funcs;
+
+        alsa_handle = LoadLib("libasound.so.2");
+        if(!alsa_handle)
+        {
+            WARN("Failed to load %s\n", "libasound.so.2");
+            return false;
+        }
+
+        error = false;
+#define LOAD_FUNC(f) do {                                                     \
+    p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(alsa_handle, #f));      \
+    if(p##f == nullptr) {                                                     \
+        error = true;                                                         \
+        missing_funcs += "\n" #f;                                             \
+    }                                                                         \
+} while(0)
+        ALSA_FUNCS(LOAD_FUNC);
+#undef LOAD_FUNC
+
+        if(error)
+        {
+            WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+            CloseLib(alsa_handle);
+            alsa_handle = nullptr;
+        }
+    }
+#endif
+
+    return !error;
+}
+
+bool AlsaBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string AlsaBackendFactory::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:
+        PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
+        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        break;
+
+    case BackendType::Capture:
+        CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+
+    return outnames;
+}
+
+BackendPtr AlsaBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new AlsaPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new AlsaCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &AlsaBackendFactory::getFactory()
+{
+    static AlsaBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/alsa.h

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

+ 0 - 83
Engine/lib/openal-soft/Alc/backends/base.c

@@ -1,83 +0,0 @@
-
-#include "config.h"
-
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alu.h"
-
-#include "backends/base.h"
-
-
-extern inline ALuint64 GetDeviceClockTime(ALCdevice *device);
-extern inline void ALCdevice_Lock(ALCdevice *device);
-extern inline void ALCdevice_Unlock(ALCdevice *device);
-
-/* Base ALCbackend method implementations. */
-void ALCbackend_Construct(ALCbackend *self, ALCdevice *device)
-{
-    int ret = almtx_init(&self->mMutex, almtx_recursive);
-    assert(ret == althrd_success);
-    self->mDevice = device;
-}
-
-void ALCbackend_Destruct(ALCbackend *self)
-{
-    almtx_destroy(&self->mMutex);
-}
-
-ALCboolean ALCbackend_reset(ALCbackend* UNUSED(self))
-{
-    return ALC_FALSE;
-}
-
-ALCenum ALCbackend_captureSamples(ALCbackend* UNUSED(self), void* UNUSED(buffer), ALCuint UNUSED(samples))
-{
-    return ALC_INVALID_DEVICE;
-}
-
-ALCuint ALCbackend_availableSamples(ALCbackend* UNUSED(self))
-{
-    return 0;
-}
-
-ClockLatency ALCbackend_getClockLatency(ALCbackend *self)
-{
-    ALCdevice *device = self->mDevice;
-    ALuint refcount;
-    ClockLatency ret;
-
-    do {
-        while(((refcount=ATOMIC_LOAD(&device->MixCount, almemory_order_acquire))&1))
-            althrd_yield();
-        ret.ClockTime = GetDeviceClockTime(device);
-        ATOMIC_THREAD_FENCE(almemory_order_acquire);
-    } while(refcount != ATOMIC_LOAD(&device->MixCount, almemory_order_relaxed));
-
-    /* NOTE: The device will generally have about all but one periods filled at
-     * any given time during playback. Without a more accurate measurement from
-     * the output, this is an okay approximation.
-     */
-    ret.Latency = device->UpdateSize * DEVICE_CLOCK_RES / device->Frequency *
-                  maxu(device->NumUpdates-1, 1);
-
-    return ret;
-}
-
-void ALCbackend_lock(ALCbackend *self)
-{
-    int ret = almtx_lock(&self->mMutex);
-    assert(ret == althrd_success);
-}
-
-void ALCbackend_unlock(ALCbackend *self)
-{
-    int ret = almtx_unlock(&self->mMutex);
-    assert(ret == althrd_success);
-}
-
-
-/* Base ALCbackendFactory method implementations. */
-void ALCbackendFactory_deinit(ALCbackendFactory* UNUSED(self))
-{
-}

+ 195 - 0
Engine/lib/openal-soft/Alc/backends/base.cpp

@@ -0,0 +1,195 @@
+
+#include "config.h"
+
+#include "base.h"
+
+#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 "aloptional.h"
+#include "atomic.h"
+#include "core/logging.h"
+
+
+bool BackendBase::reset()
+{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
+
+void BackendBase::captureSamples(al::byte*, uint)
+{ }
+
+uint BackendBase::availableSamples()
+{ return 0; }
+
+ClockLatency BackendBase::getClockLatency()
+{
+    ClockLatency ret;
+
+    uint refcount;
+    do {
+        refcount = mDevice->waitForMix();
+        ret.ClockTime = GetDeviceClockTime(mDevice);
+        std::atomic_thread_fence(std::memory_order_acquire);
+    } while(refcount != ReadRef(mDevice->MixCount));
+
+    /* NOTE: The device will generally have about all but one periods filled at
+     * any given time during playback. Without a more accurate measurement from
+     * the output, this is an okay approximation.
+     */
+    ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
+        std::chrono::seconds::zero());
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+void BackendBase::setDefaultWFXChannelOrder()
+{
+    mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 0;
+        break;
+    case DevFmtStereo:
+        mDevice->RealOut.ChannelIndex[FrontLeft]  = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+        break;
+    case DevFmtQuad:
+        mDevice->RealOut.ChannelIndex[FrontLeft]  = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight] = 1;
+        mDevice->RealOut.ChannelIndex[BackLeft]   = 2;
+        mDevice->RealOut.ChannelIndex[BackRight]  = 3;
+        break;
+    case DevFmtX51:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+        mDevice->RealOut.ChannelIndex[LFE]         = 3;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 4;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 5;
+        break;
+    case 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;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+        mDevice->RealOut.ChannelIndex[LFE]         = 3;
+        mDevice->RealOut.ChannelIndex[BackCenter]  = 4;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 5;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 6;
+        break;
+    case DevFmtX71:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
+        mDevice->RealOut.ChannelIndex[LFE]         = 3;
+        mDevice->RealOut.ChannelIndex[BackLeft]    = 4;
+        mDevice->RealOut.ChannelIndex[BackRight]   = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 7;
+        break;
+    case DevFmtAmbi3D:
+        break;
+    }
+}
+
+void BackendBase::setDefaultChannelOrder()
+{
+    mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtX51Rear:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[BackLeft]    = 2;
+        mDevice->RealOut.ChannelIndex[BackRight]   = 3;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
+        mDevice->RealOut.ChannelIndex[LFE]         = 5;
+        return;
+    case DevFmtX71:
+        mDevice->RealOut.ChannelIndex[FrontLeft]   = 0;
+        mDevice->RealOut.ChannelIndex[FrontRight]  = 1;
+        mDevice->RealOut.ChannelIndex[BackLeft]    = 2;
+        mDevice->RealOut.ChannelIndex[BackRight]   = 3;
+        mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
+        mDevice->RealOut.ChannelIndex[LFE]         = 5;
+        mDevice->RealOut.ChannelIndex[SideLeft]    = 6;
+        mDevice->RealOut.ChannelIndex[SideRight]   = 7;
+        return;
+
+    /* Same as WFX order */
+    case DevFmtMono:
+    case DevFmtStereo:
+    case DevFmtQuad:
+    case DevFmtX51:
+    case DevFmtX61:
+    case DevFmtAmbi3D:
+        setDefaultWFXChannelOrder();
+        break;
+    }
+}
+
+#ifdef _WIN32
+void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
+{
+    auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
+    {
+        switch(chanbit)
+        {
+        case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
+        case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
+        case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
+        case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
+        case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
+        case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
+        case SPEAKER_FRONT_LEFT_OF_CENTER: break;
+        case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
+        case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
+        case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
+        case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
+        case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
+        case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
+        case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
+        case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
+        case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
+        case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
+        case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
+        }
+        WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
+        return al::nullopt;
+    };
+
+    const uint numchans{mDevice->channelsFromFmt()};
+    uint idx{0};
+    while(chanmask)
+    {
+        const int bit{al::countr_zero(chanmask)};
+        const uint mask{1u << bit};
+        chanmask &= ~mask;
+
+        if(auto label = get_channel(mask))
+        {
+            mDevice->RealOut.ChannelIndex[*label] = idx;
+            if(++idx == numchans) break;
+        }
+    }
+}
+#endif

+ 88 - 137
Engine/lib/openal-soft/Alc/backends/base.h

@@ -1,168 +1,119 @@
-#ifndef AL_BACKENDS_BASE_H
-#define AL_BACKENDS_BASE_H
+#ifndef ALC_BACKENDS_BASE_H
+#define ALC_BACKENDS_BASE_H
 
-#include "alMain.h"
-#include "threads.h"
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <string>
 
+#include "albyte.h"
+#include "alcmain.h"
+#include "core/except.h"
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct ClockLatency {
-    ALint64 ClockTime;
-    ALint64 Latency;
-} ClockLatency;
 
-/* Helper to get the current clock time from the device's ClockBase, and
- * SamplesDone converted from the sample rate.
- */
-inline ALuint64 GetDeviceClockTime(ALCdevice *device)
-{
-    return device->ClockBase + (device->SamplesDone * DEVICE_CLOCK_RES /
-                                device->Frequency);
-}
+using uint = unsigned int;
 
+struct ClockLatency {
+    std::chrono::nanoseconds ClockTime;
+    std::chrono::nanoseconds Latency;
+};
 
-struct ALCbackendVtable;
-
-typedef struct ALCbackend {
-    const struct ALCbackendVtable *vtbl;
+struct BackendBase {
+    virtual void open(const char *name) = 0;
 
-    ALCdevice *mDevice;
+    virtual bool reset();
+    virtual void start() = 0;
+    virtual void stop() = 0;
 
-    almtx_t mMutex;
-} ALCbackend;
+    virtual void captureSamples(al::byte *buffer, uint samples);
+    virtual uint availableSamples();
 
-void ALCbackend_Construct(ALCbackend *self, ALCdevice *device);
-void ALCbackend_Destruct(ALCbackend *self);
-ALCboolean ALCbackend_reset(ALCbackend *self);
-ALCenum ALCbackend_captureSamples(ALCbackend *self, void *buffer, ALCuint samples);
-ALCuint ALCbackend_availableSamples(ALCbackend *self);
-ClockLatency ALCbackend_getClockLatency(ALCbackend *self);
-void ALCbackend_lock(ALCbackend *self);
-void ALCbackend_unlock(ALCbackend *self);
+    virtual ClockLatency getClockLatency();
 
-struct ALCbackendVtable {
-    void (*const Destruct)(ALCbackend*);
+    ALCdevice *const mDevice;
 
-    ALCenum (*const open)(ALCbackend*, const ALCchar*);
+    BackendBase(ALCdevice *device) noexcept : mDevice{device} { }
+    virtual ~BackendBase() = default;
 
-    ALCboolean (*const reset)(ALCbackend*);
-    ALCboolean (*const start)(ALCbackend*);
-    void (*const stop)(ALCbackend*);
+protected:
+    /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
+    void setDefaultChannelOrder();
+    /** Sets the default channel order used by WaveFormatEx. */
+    void setDefaultWFXChannelOrder();
 
-    ALCenum (*const captureSamples)(ALCbackend*, void*, ALCuint);
-    ALCuint (*const availableSamples)(ALCbackend*);
+#ifdef _WIN32
+    /** Sets the channel order given the WaveFormatEx mask. */
+    void setChannelOrderFromWFXMask(uint chanmask);
+#endif
+};
+using BackendPtr = std::unique_ptr<BackendBase>;
 
-    ClockLatency (*const getClockLatency)(ALCbackend*);
+enum class BackendType {
+    Playback,
+    Capture
+};
 
-    void (*const lock)(ALCbackend*);
-    void (*const unlock)(ALCbackend*);
 
-    void (*const Delete)(void*);
-};
+/* 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)
+{
+    using std::chrono::seconds;
+    using std::chrono::nanoseconds;
 
-#define DEFINE_ALCBACKEND_VTABLE(T)                                           \
-DECLARE_THUNK(T, ALCbackend, void, Destruct)                                  \
-DECLARE_THUNK1(T, ALCbackend, ALCenum, open, const ALCchar*)                  \
-DECLARE_THUNK(T, ALCbackend, ALCboolean, reset)                               \
-DECLARE_THUNK(T, ALCbackend, ALCboolean, start)                               \
-DECLARE_THUNK(T, ALCbackend, void, stop)                                      \
-DECLARE_THUNK2(T, ALCbackend, ALCenum, captureSamples, void*, ALCuint)        \
-DECLARE_THUNK(T, ALCbackend, ALCuint, availableSamples)                       \
-DECLARE_THUNK(T, ALCbackend, ClockLatency, getClockLatency)                   \
-DECLARE_THUNK(T, ALCbackend, void, lock)                                      \
-DECLARE_THUNK(T, ALCbackend, void, unlock)                                    \
-static void T##_ALCbackend_Delete(void *ptr)                                  \
-{ T##_Delete(STATIC_UPCAST(T, ALCbackend, (ALCbackend*)ptr)); }               \
-                                                                              \
-static const struct ALCbackendVtable T##_ALCbackend_vtable = {                \
-    T##_ALCbackend_Destruct,                                                  \
-                                                                              \
-    T##_ALCbackend_open,                                                      \
-    T##_ALCbackend_reset,                                                     \
-    T##_ALCbackend_start,                                                     \
-    T##_ALCbackend_stop,                                                      \
-    T##_ALCbackend_captureSamples,                                            \
-    T##_ALCbackend_availableSamples,                                          \
-    T##_ALCbackend_getClockLatency,                                           \
-    T##_ALCbackend_lock,                                                      \
-    T##_ALCbackend_unlock,                                                    \
-                                                                              \
-    T##_ALCbackend_Delete,                                                    \
+    auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
+    return device->ClockBase + ns;
 }
 
-
-typedef enum ALCbackend_Type {
-    ALCbackend_Playback,
-    ALCbackend_Capture,
-    ALCbackend_Loopback
-} ALCbackend_Type;
+/* Helper to get the device latency from the backend, including any fixed
+ * latency from post-processing.
+ */
+inline ClockLatency GetClockLatency(ALCdevice *device)
+{
+    BackendBase *backend{device->Backend.get()};
+    ClockLatency ret{backend->getClockLatency()};
+    ret.Latency += device->FixedLatency;
+    return ret;
+}
 
 
-struct ALCbackendFactoryVtable;
+struct BackendFactory {
+    virtual bool init() = 0;
 
-typedef struct ALCbackendFactory {
-    const struct ALCbackendFactoryVtable *vtbl;
-} ALCbackendFactory;
+    virtual bool querySupport(BackendType type) = 0;
 
-void ALCbackendFactory_deinit(ALCbackendFactory *self);
+    virtual std::string probe(BackendType type) = 0;
 
-struct ALCbackendFactoryVtable {
-    ALCboolean (*const init)(ALCbackendFactory *self);
-    void (*const deinit)(ALCbackendFactory *self);
+    virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
 
-    ALCboolean (*const querySupport)(ALCbackendFactory *self, ALCbackend_Type type);
+protected:
+    virtual ~BackendFactory() = default;
+};
 
-    void (*const probe)(ALCbackendFactory *self, enum DevProbe type);
+namespace al {
 
-    ALCbackend* (*const createBackend)(ALCbackendFactory *self, ALCdevice *device, ALCbackend_Type type);
+enum class backend_error {
+    NoDevice,
+    DeviceError,
+    OutOfMemory
 };
 
-#define DEFINE_ALCBACKENDFACTORY_VTABLE(T)                                    \
-DECLARE_THUNK(T, ALCbackendFactory, ALCboolean, init)                         \
-DECLARE_THUNK(T, ALCbackendFactory, void, deinit)                             \
-DECLARE_THUNK1(T, ALCbackendFactory, ALCboolean, querySupport, ALCbackend_Type) \
-DECLARE_THUNK1(T, ALCbackendFactory, void, probe, enum DevProbe)              \
-DECLARE_THUNK2(T, ALCbackendFactory, ALCbackend*, createBackend, ALCdevice*, ALCbackend_Type) \
-                                                                              \
-static const struct ALCbackendFactoryVtable T##_ALCbackendFactory_vtable = {  \
-    T##_ALCbackendFactory_init,                                               \
-    T##_ALCbackendFactory_deinit,                                             \
-    T##_ALCbackendFactory_querySupport,                                       \
-    T##_ALCbackendFactory_probe,                                              \
-    T##_ALCbackendFactory_createBackend,                                      \
-}
-
+class backend_exception final : public base_exception {
+    backend_error mErrorCode;
+
+public:
+    [[gnu::format(printf, 3, 4)]]
+    backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
+    {
+        std::va_list args;
+        va_start(args, msg);
+        setMessage(msg, args);
+        va_end(args);
+    }
+    backend_error errorCode() const noexcept { return mErrorCode; }
+};
 
-ALCbackendFactory *ALCpulseBackendFactory_getFactory(void);
-ALCbackendFactory *ALCalsaBackendFactory_getFactory(void);
-ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void);
-ALCbackendFactory *ALCossBackendFactory_getFactory(void);
-ALCbackendFactory *ALCjackBackendFactory_getFactory(void);
-ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void);
-ALCbackendFactory *ALCsndioBackendFactory_getFactory(void);
-ALCbackendFactory *ALCqsaBackendFactory_getFactory(void);
-ALCbackendFactory *ALCwasapiBackendFactory_getFactory(void);
-ALCbackendFactory *ALCdsoundBackendFactory_getFactory(void);
-ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void);
-ALCbackendFactory *ALCportBackendFactory_getFactory(void);
-ALCbackendFactory *ALCopenslBackendFactory_getFactory(void);
-ALCbackendFactory *ALCnullBackendFactory_getFactory(void);
-ALCbackendFactory *ALCwaveBackendFactory_getFactory(void);
-ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void);
-ALCbackendFactory *ALCloopbackFactory_getFactory(void);
-
-
-inline void ALCdevice_Lock(ALCdevice *device)
-{ V0(device->Backend,lock)(); }
-
-inline void ALCdevice_Unlock(ALCdevice *device)
-{ V0(device->Backend,unlock)(); }
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+} // namespace al
 
-#endif /* AL_BACKENDS_BASE_H */
+#endif /* ALC_BACKENDS_BASE_H */

+ 0 - 810
Engine/lib/openal-soft/Alc/backends/coreaudio.c

@@ -1,810 +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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-
-#include <CoreServices/CoreServices.h>
-#include <unistd.h>
-#include <AudioUnit/AudioUnit.h>
-#include <AudioToolbox/AudioToolbox.h>
-
-#include "backends/base.h"
-
-
-static const ALCchar ca_device[] = "CoreAudio Default";
-
-
-typedef struct ALCcoreAudioPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    AudioUnit audioUnit;
-
-    ALuint frameSize;
-    AudioStreamBasicDescription format;    // This is the OpenAL format as a CoreAudio ASBD
-} ALCcoreAudioPlayback;
-
-static void ALCcoreAudioPlayback_Construct(ALCcoreAudioPlayback *self, ALCdevice *device);
-static void ALCcoreAudioPlayback_Destruct(ALCcoreAudioPlayback *self);
-static ALCenum ALCcoreAudioPlayback_open(ALCcoreAudioPlayback *self, const ALCchar *name);
-static ALCboolean ALCcoreAudioPlayback_reset(ALCcoreAudioPlayback *self);
-static ALCboolean ALCcoreAudioPlayback_start(ALCcoreAudioPlayback *self);
-static void ALCcoreAudioPlayback_stop(ALCcoreAudioPlayback *self);
-static DECLARE_FORWARD2(ALCcoreAudioPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCcoreAudioPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCcoreAudioPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCcoreAudioPlayback);
-
-
-static void ALCcoreAudioPlayback_Construct(ALCcoreAudioPlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCcoreAudioPlayback, ALCbackend, self);
-
-    self->frameSize = 0;
-    memset(&self->format, 0, sizeof(self->format));
-}
-
-static void ALCcoreAudioPlayback_Destruct(ALCcoreAudioPlayback *self)
-{
-    AudioUnitUninitialize(self->audioUnit);
-    AudioComponentInstanceDispose(self->audioUnit);
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static OSStatus ALCcoreAudioPlayback_MixerProc(void *inRefCon,
-  AudioUnitRenderActionFlags* UNUSED(ioActionFlags), const AudioTimeStamp* UNUSED(inTimeStamp),
-  UInt32 UNUSED(inBusNumber), UInt32 UNUSED(inNumberFrames), AudioBufferList *ioData)
-{
-    ALCcoreAudioPlayback *self = inRefCon;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-
-    ALCcoreAudioPlayback_lock(self);
-    aluMixData(device, ioData->mBuffers[0].mData,
-               ioData->mBuffers[0].mDataByteSize / self->frameSize);
-    ALCcoreAudioPlayback_unlock(self);
-
-    return noErr;
-}
-
-
-static ALCenum ALCcoreAudioPlayback_open(ALCcoreAudioPlayback *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    AudioComponentDescription desc;
-    AudioComponent comp;
-    OSStatus err;
-
-    if(!name)
-        name = ca_device;
-    else if(strcmp(name, ca_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    /* open the default output unit */
-    desc.componentType = kAudioUnitType_Output;
-    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
-    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
-    desc.componentFlags = 0;
-    desc.componentFlagsMask = 0;
-
-    comp = AudioComponentFindNext(NULL, &desc);
-    if(comp == NULL)
-    {
-        ERR("AudioComponentFindNext failed\n");
-        return ALC_INVALID_VALUE;
-    }
-
-    err = AudioComponentInstanceNew(comp, &self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioComponentInstanceNew failed\n");
-        return ALC_INVALID_VALUE;
-    }
-
-    /* init and start the default audio unit... */
-    err = AudioUnitInitialize(self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioUnitInitialize failed\n");
-        AudioComponentInstanceDispose(self->audioUnit);
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCcoreAudioPlayback_reset(ALCcoreAudioPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    AudioStreamBasicDescription streamFormat;
-    AURenderCallbackStruct input;
-    OSStatus err;
-    UInt32 size;
-
-    err = AudioUnitUninitialize(self->audioUnit);
-    if(err != noErr)
-        ERR("-- AudioUnitUninitialize failed.\n");
-
-    /* retrieve default output unit's properties (output side) */
-    size = sizeof(AudioStreamBasicDescription);
-    err = AudioUnitGetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, &size);
-    if(err != noErr || size != sizeof(AudioStreamBasicDescription))
-    {
-        ERR("AudioUnitGetProperty failed\n");
-        return ALC_FALSE;
-    }
-
-#if 0
-    TRACE("Output streamFormat of default output unit -\n");
-    TRACE("  streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
-    TRACE("  streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
-    TRACE("  streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
-    TRACE("  streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
-    TRACE("  streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
-    TRACE("  streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
-#endif
-
-    /* set default output unit's input side to match output side */
-    err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, size);
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        return ALC_FALSE;
-    }
-
-    if(device->Frequency != streamFormat.mSampleRate)
-    {
-        device->NumUpdates = (ALuint)((ALuint64)device->NumUpdates *
-                                      streamFormat.mSampleRate /
-                                      device->Frequency);
-        device->Frequency = streamFormat.mSampleRate;
-    }
-
-    /* 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:
-            device->FmtChans = DevFmtMono;
-            break;
-        case 2:
-            device->FmtChans = DevFmtStereo;
-            break;
-        case 4:
-            device->FmtChans = DevFmtQuad;
-            break;
-        case 6:
-            device->FmtChans = DevFmtX51;
-            break;
-        case 7:
-            device->FmtChans = DevFmtX61;
-            break;
-        case 8:
-            device->FmtChans = DevFmtX71;
-            break;
-        default:
-            ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
-            device->FmtChans = DevFmtStereo;
-            streamFormat.mChannelsPerFrame = 2;
-            break;
-    }
-    SetDefaultWFXChannelOrder(device);
-
-    /* use channel count and sample rate from the default output unit's current
-     * parameters, but reset everything else */
-    streamFormat.mFramesPerPacket = 1;
-    streamFormat.mFormatFlags = 0;
-    switch(device->FmtType)
-    {
-        case DevFmtUByte:
-            device->FmtType = DevFmtByte;
-            /* fall-through */
-        case DevFmtByte:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-            streamFormat.mBitsPerChannel = 8;
-            break;
-        case DevFmtUShort:
-            device->FmtType = DevFmtShort;
-            /* fall-through */
-        case DevFmtShort:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-            streamFormat.mBitsPerChannel = 16;
-            break;
-        case DevFmtUInt:
-            device->FmtType = DevFmtInt;
-            /* fall-through */
-        case DevFmtInt:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-            streamFormat.mBitsPerChannel = 32;
-            break;
-        case DevFmtFloat:
-            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
-            streamFormat.mBitsPerChannel = 32;
-            break;
-    }
-    streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
-                                  streamFormat.mBitsPerChannel / 8;
-    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
-    streamFormat.mFormatID = kAudioFormatLinearPCM;
-    streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
-                                 kLinearPCMFormatFlagIsPacked;
-
-    err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(AudioStreamBasicDescription));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        return ALC_FALSE;
-    }
-
-    /* setup callback */
-    self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    input.inputProc = ALCcoreAudioPlayback_MixerProc;
-    input.inputProcRefCon = self;
-
-    err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        return ALC_FALSE;
-    }
-
-    /* init the default audio unit... */
-    err = AudioUnitInitialize(self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioUnitInitialize failed\n");
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCcoreAudioPlayback_start(ALCcoreAudioPlayback *self)
-{
-    OSStatus err = AudioOutputUnitStart(self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioOutputUnitStart failed\n");
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCcoreAudioPlayback_stop(ALCcoreAudioPlayback *self)
-{
-    OSStatus err = AudioOutputUnitStop(self->audioUnit);
-    if(err != noErr)
-        ERR("AudioOutputUnitStop failed\n");
-}
-
-
-
-
-typedef struct ALCcoreAudioCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    AudioUnit audioUnit;
-
-    ALuint frameSize;
-    ALdouble sampleRateRatio;              // Ratio of hardware sample rate / requested sample rate
-    AudioStreamBasicDescription format;    // This is the OpenAL format as a CoreAudio ASBD
-
-    AudioConverterRef audioConverter;      // Sample rate converter if needed
-    AudioBufferList *bufferList;           // Buffer for data coming from the input device
-    ALCvoid *resampleBuffer;               // Buffer for returned RingBuffer data when resampling
-
-    ll_ringbuffer_t *ring;
-} ALCcoreAudioCapture;
-
-static void ALCcoreAudioCapture_Construct(ALCcoreAudioCapture *self, ALCdevice *device);
-static void ALCcoreAudioCapture_Destruct(ALCcoreAudioCapture *self);
-static ALCenum ALCcoreAudioCapture_open(ALCcoreAudioCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCcoreAudioCapture_start(ALCcoreAudioCapture *self);
-static void ALCcoreAudioCapture_stop(ALCcoreAudioCapture *self);
-static ALCenum ALCcoreAudioCapture_captureSamples(ALCcoreAudioCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCcoreAudioCapture_availableSamples(ALCcoreAudioCapture *self);
-static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCcoreAudioCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCcoreAudioCapture)
-
-DEFINE_ALCBACKEND_VTABLE(ALCcoreAudioCapture);
-
-
-static AudioBufferList *allocate_buffer_list(UInt32 channelCount, UInt32 byteSize)
-{
-    AudioBufferList *list;
-
-    list = calloc(1, FAM_SIZE(AudioBufferList, mBuffers, 1) + byteSize);
-    if(list)
-    {
-        list->mNumberBuffers = 1;
-
-        list->mBuffers[0].mNumberChannels = channelCount;
-        list->mBuffers[0].mDataByteSize = byteSize;
-        list->mBuffers[0].mData = &list->mBuffers[1];
-    }
-    return list;
-}
-
-static void destroy_buffer_list(AudioBufferList *list)
-{
-    free(list);
-}
-
-
-static void ALCcoreAudioCapture_Construct(ALCcoreAudioCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCcoreAudioCapture, ALCbackend, self);
-
-    self->audioUnit = 0;
-    self->audioConverter = NULL;
-    self->bufferList = NULL;
-    self->resampleBuffer = NULL;
-    self->ring = NULL;
-}
-
-static void ALCcoreAudioCapture_Destruct(ALCcoreAudioCapture *self)
-{
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-
-    free(self->resampleBuffer);
-    self->resampleBuffer = NULL;
-
-    destroy_buffer_list(self->bufferList);
-    self->bufferList = NULL;
-
-    if(self->audioConverter)
-        AudioConverterDispose(self->audioConverter);
-    self->audioConverter = NULL;
-
-    if(self->audioUnit)
-        AudioComponentInstanceDispose(self->audioUnit);
-    self->audioUnit = 0;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static OSStatus ALCcoreAudioCapture_RecordProc(void *inRefCon,
-  AudioUnitRenderActionFlags* UNUSED(ioActionFlags),
-  const AudioTimeStamp *inTimeStamp, UInt32 UNUSED(inBusNumber),
-  UInt32 inNumberFrames, AudioBufferList* UNUSED(ioData))
-{
-    ALCcoreAudioCapture *self = inRefCon;
-    AudioUnitRenderActionFlags flags = 0;
-    OSStatus err;
-
-    // fill the bufferList with data from the input device
-    err = AudioUnitRender(self->audioUnit, &flags, inTimeStamp, 1, inNumberFrames, self->bufferList);
-    if(err != noErr)
-    {
-        ERR("AudioUnitRender error: %d\n", err);
-        return err;
-    }
-
-    ll_ringbuffer_write(self->ring, self->bufferList->mBuffers[0].mData, inNumberFrames);
-
-    return noErr;
-}
-
-static OSStatus ALCcoreAudioCapture_ConvertCallback(AudioConverterRef UNUSED(inAudioConverter),
-  UInt32 *ioNumberDataPackets, AudioBufferList *ioData,
-  AudioStreamPacketDescription** UNUSED(outDataPacketDescription),
-  void *inUserData)
-{
-    ALCcoreAudioCapture *self = inUserData;
-
-    // Read from the ring buffer and store temporarily in a large buffer
-    ll_ringbuffer_read(self->ring, self->resampleBuffer, *ioNumberDataPackets);
-
-    // Set the input data
-    ioData->mNumberBuffers = 1;
-    ioData->mBuffers[0].mNumberChannels = self->format.mChannelsPerFrame;
-    ioData->mBuffers[0].mData = self->resampleBuffer;
-    ioData->mBuffers[0].mDataByteSize = (*ioNumberDataPackets) * self->format.mBytesPerFrame;
-
-    return noErr;
-}
-
-
-static ALCenum ALCcoreAudioCapture_open(ALCcoreAudioCapture *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    AudioStreamBasicDescription requestedFormat;  // The application requested format
-    AudioStreamBasicDescription hardwareFormat;   // The hardware format
-    AudioStreamBasicDescription outputFormat;     // The AudioUnit output format
-    AURenderCallbackStruct input;
-    AudioComponentDescription desc;
-    AudioDeviceID inputDevice;
-    UInt32 outputFrameCount;
-    UInt32 propertySize;
-    AudioObjectPropertyAddress propertyAddress;
-    UInt32 enableIO;
-    AudioComponent comp;
-    OSStatus err;
-
-    if(!name)
-        name = ca_device;
-    else if(strcmp(name, ca_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    desc.componentType = kAudioUnitType_Output;
-    desc.componentSubType = kAudioUnitSubType_HALOutput;
-    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
-    desc.componentFlags = 0;
-    desc.componentFlagsMask = 0;
-
-    // Search for component with given description
-    comp = AudioComponentFindNext(NULL, &desc);
-    if(comp == NULL)
-    {
-        ERR("AudioComponentFindNext failed\n");
-        return ALC_INVALID_VALUE;
-    }
-
-    // Open the component
-    err = AudioComponentInstanceNew(comp, &self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioComponentInstanceNew failed\n");
-        goto error;
-    }
-
-    // Turn off AudioUnit output
-    enableIO = 0;
-    err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        goto error;
-    }
-
-    // Turn on AudioUnit input
-    enableIO = 1;
-    err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        goto error;
-    }
-
-    // Get the default input device
-
-    propertySize = sizeof(AudioDeviceID);
-    propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
-    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
-    propertyAddress.mElement = kAudioObjectPropertyElementMaster;
-
-    err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &inputDevice);
-    if(err != noErr)
-    {
-        ERR("AudioObjectGetPropertyData failed\n");
-        goto error;
-    }
-
-    if(inputDevice == kAudioDeviceUnknown)
-    {
-        ERR("No input device found\n");
-        goto error;
-    }
-
-    // Track the input device
-    err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        goto error;
-    }
-
-    // set capture callback
-    input.inputProc = ALCcoreAudioCapture_RecordProc;
-    input.inputProcRefCon = self;
-
-    err = AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        goto error;
-    }
-
-    // Initialize the device
-    err = AudioUnitInitialize(self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioUnitInitialize failed\n");
-        goto error;
-    }
-
-    // Get the hardware format
-    propertySize = sizeof(AudioStreamBasicDescription);
-    err = AudioUnitGetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &hardwareFormat, &propertySize);
-    if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
-    {
-        ERR("AudioUnitGetProperty failed\n");
-        goto error;
-    }
-
-    // Set up the requested format description
-    switch(device->FmtType)
-    {
-        case DevFmtUByte:
-            requestedFormat.mBitsPerChannel = 8;
-            requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
-            break;
-        case DevFmtShort:
-            requestedFormat.mBitsPerChannel = 16;
-            requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
-            break;
-        case DevFmtInt:
-            requestedFormat.mBitsPerChannel = 32;
-            requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
-            break;
-        case DevFmtFloat:
-            requestedFormat.mBitsPerChannel = 32;
-            requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
-            break;
-        case DevFmtByte:
-        case DevFmtUShort:
-        case DevFmtUInt:
-            ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType));
-            goto error;
-    }
-
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            requestedFormat.mChannelsPerFrame = 1;
-            break;
-        case DevFmtStereo:
-            requestedFormat.mChannelsPerFrame = 2;
-            break;
-
-        case DevFmtQuad:
-        case DevFmtX51:
-        case DevFmtX51Rear:
-        case DevFmtX61:
-        case DevFmtX71:
-        case DevFmtAmbi3D:
-            ERR("%s not supported\n", DevFmtChannelsString(device->FmtChans));
-            goto error;
-    }
-
-    requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
-    requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
-    requestedFormat.mSampleRate = device->Frequency;
-    requestedFormat.mFormatID = kAudioFormatLinearPCM;
-    requestedFormat.mReserved = 0;
-    requestedFormat.mFramesPerPacket = 1;
-
-    // save requested format description for later use
-    self->format = requestedFormat;
-    self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    // Use intermediate format for sample rate conversion (outputFormat)
-    // Set sample rate to the same as hardware for resampling later
-    outputFormat = requestedFormat;
-    outputFormat.mSampleRate = hardwareFormat.mSampleRate;
-
-    // Determine sample rate ratio for resampling
-    self->sampleRateRatio = outputFormat.mSampleRate / device->Frequency;
-
-    // The output format should be the requested format, but using the hardware sample rate
-    // This is because the AudioUnit will automatically scale other properties, except for sample rate
-    err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, (void *)&outputFormat, sizeof(outputFormat));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed\n");
-        goto error;
-    }
-
-    // Set the AudioUnit output format frame count
-    outputFrameCount = device->UpdateSize * self->sampleRateRatio;
-    err = AudioUnitSetProperty(self->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
-    if(err != noErr)
-    {
-        ERR("AudioUnitSetProperty failed: %d\n", err);
-        goto error;
-    }
-
-    // Set up sample converter
-    err = AudioConverterNew(&outputFormat, &requestedFormat, &self->audioConverter);
-    if(err != noErr)
-    {
-        ERR("AudioConverterNew failed: %d\n", err);
-        goto error;
-    }
-
-    // Create a buffer for use in the resample callback
-    self->resampleBuffer = malloc(device->UpdateSize * self->frameSize * self->sampleRateRatio);
-
-    // Allocate buffer for the AudioUnit output
-    self->bufferList = allocate_buffer_list(outputFormat.mChannelsPerFrame, device->UpdateSize * self->frameSize * self->sampleRateRatio);
-    if(self->bufferList == NULL)
-        goto error;
-
-    self->ring = ll_ringbuffer_create(
-        (size_t)ceil(device->UpdateSize*self->sampleRateRatio*device->NumUpdates),
-        self->frameSize, false
-    );
-    if(!self->ring) goto error;
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-
-error:
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-    free(self->resampleBuffer);
-    self->resampleBuffer = NULL;
-    destroy_buffer_list(self->bufferList);
-    self->bufferList = NULL;
-
-    if(self->audioConverter)
-        AudioConverterDispose(self->audioConverter);
-    self->audioConverter = NULL;
-    if(self->audioUnit)
-        AudioComponentInstanceDispose(self->audioUnit);
-    self->audioUnit = 0;
-
-    return ALC_INVALID_VALUE;
-}
-
-
-static ALCboolean ALCcoreAudioCapture_start(ALCcoreAudioCapture *self)
-{
-    OSStatus err = AudioOutputUnitStart(self->audioUnit);
-    if(err != noErr)
-    {
-        ERR("AudioOutputUnitStart failed\n");
-        return ALC_FALSE;
-    }
-    return ALC_TRUE;
-}
-
-static void ALCcoreAudioCapture_stop(ALCcoreAudioCapture *self)
-{
-    OSStatus err = AudioOutputUnitStop(self->audioUnit);
-    if(err != noErr)
-        ERR("AudioOutputUnitStop failed\n");
-}
-
-static ALCenum ALCcoreAudioCapture_captureSamples(ALCcoreAudioCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    union {
-        ALbyte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)];
-        AudioBufferList list;
-    } audiobuf = { { 0 } };
-    UInt32 frameCount;
-    OSStatus err;
-
-    // If no samples are requested, just return
-    if(samples == 0) return ALC_NO_ERROR;
-
-    // Point the resampling buffer to the capture buffer
-    audiobuf.list.mNumberBuffers = 1;
-    audiobuf.list.mBuffers[0].mNumberChannels = self->format.mChannelsPerFrame;
-    audiobuf.list.mBuffers[0].mDataByteSize = samples * self->frameSize;
-    audiobuf.list.mBuffers[0].mData = buffer;
-
-    // Resample into another AudioBufferList
-    frameCount = samples;
-    err = AudioConverterFillComplexBuffer(self->audioConverter,
-        ALCcoreAudioCapture_ConvertCallback, self, &frameCount, &audiobuf.list, NULL
-    );
-    if(err != noErr)
-    {
-        ERR("AudioConverterFillComplexBuffer error: %d\n", err);
-        return ALC_INVALID_VALUE;
-    }
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCcoreAudioCapture_availableSamples(ALCcoreAudioCapture *self)
-{
-    return ll_ringbuffer_read_space(self->ring) / self->sampleRateRatio;
-}
-
-
-typedef struct ALCcoreAudioBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCcoreAudioBackendFactory;
-#define ALCCOREAUDIOBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCcoreAudioBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void);
-
-static ALCboolean ALCcoreAudioBackendFactory_init(ALCcoreAudioBackendFactory *self);
-static DECLARE_FORWARD(ALCcoreAudioBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCcoreAudioBackendFactory_querySupport(ALCcoreAudioBackendFactory *self, ALCbackend_Type type);
-static void ALCcoreAudioBackendFactory_probe(ALCcoreAudioBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCcoreAudioBackendFactory_createBackend(ALCcoreAudioBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCcoreAudioBackendFactory);
-
-
-ALCbackendFactory *ALCcoreAudioBackendFactory_getFactory(void)
-{
-    static ALCcoreAudioBackendFactory factory = ALCCOREAUDIOBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCcoreAudioBackendFactory_init(ALCcoreAudioBackendFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCcoreAudioBackendFactory_querySupport(ALCcoreAudioBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCcoreAudioBackendFactory_probe(ALCcoreAudioBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(ca_device);
-            break;
-        case CAPTURE_DEVICE_PROBE:
-            AppendCaptureDeviceList(ca_device);
-            break;
-    }
-}
-
-static ALCbackend* ALCcoreAudioBackendFactory_createBackend(ALCcoreAudioBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCcoreAudioPlayback *backend;
-        NEW_OBJ(backend, ALCcoreAudioPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCcoreAudioCapture *backend;
-        NEW_OBJ(backend, ALCcoreAudioCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 686 - 0
Engine/lib/openal-soft/Alc/backends/coreaudio.cpp

@@ -0,0 +1,686 @@
+/**
+ * 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 "backends/coreaudio.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmath>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "converter.h"
+#include "core/logging.h"
+#include "backends/base.h"
+
+#include <unistd.h>
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+
+namespace {
+
+static const char ca_device[] = "CoreAudio Default";
+
+
+struct CoreAudioPlayback final : public BackendBase {
+    CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~CoreAudioPlayback() override;
+
+    OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
+        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
+        AudioBufferList *ioData) noexcept;
+    static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
+        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
+        AudioBufferList *ioData) noexcept
+    {
+        return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
+            inBusNumber, inNumberFrames, ioData);
+    }
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    AudioUnit mAudioUnit{};
+
+    uint mFrameSize{0u};
+    AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
+
+    DEF_NEWDEL(CoreAudioPlayback)
+};
+
+CoreAudioPlayback::~CoreAudioPlayback()
+{
+    AudioUnitUninitialize(mAudioUnit);
+    AudioComponentInstanceDispose(mAudioUnit);
+}
+
+
+OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
+    UInt32, AudioBufferList *ioData) noexcept
+{
+    for(size_t i{0};i < ioData->mNumberBuffers;++i)
+    {
+        auto &buffer = ioData->mBuffers[i];
+        mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
+            buffer.mNumberChannels);
+    }
+    return noErr;
+}
+
+
+void CoreAudioPlayback::open(const char *name)
+{
+    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};
+
+    /* open the default output unit */
+    AudioComponentDescription desc{};
+    desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IOS
+    desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#else
+    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+#endif
+    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+    desc.componentFlags = 0;
+    desc.componentFlagsMask = 0;
+
+    AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
+    if(comp == nullptr)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
+
+    OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
+    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(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not initialize audio unit: %u", err};
+
+    mDevice->DeviceName = name;
+}
+
+bool CoreAudioPlayback::reset()
+{
+    OSStatus err{AudioUnitUninitialize(mAudioUnit)};
+    if(err != noErr)
+        ERR("-- AudioUnitUninitialize failed.\n");
+
+    /* retrieve default output unit's properties (output side) */
+    AudioStreamBasicDescription streamFormat{};
+    auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
+    err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
+        0, &streamFormat, &size);
+    if(err != noErr || size != sizeof(AudioStreamBasicDescription))
+    {
+        ERR("AudioUnitGetProperty failed\n");
+        return false;
+    }
+
+#if 0
+    TRACE("Output streamFormat of default output unit -\n");
+    TRACE("  streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
+    TRACE("  streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
+    TRACE("  streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
+    TRACE("  streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
+    TRACE("  streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
+    TRACE("  streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
+#endif
+
+    /* 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;
+    }
+
+    if(mDevice->Frequency != streamFormat.mSampleRate)
+    {
+        mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
+            streamFormat.mSampleRate / mDevice->Frequency);
+        mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
+    }
+
+    /* 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();
+
+    /* use channel count and sample rate from the default output unit's current
+     * parameters, but reset everything else */
+    streamFormat.mFramesPerPacket = 1;
+    streamFormat.mFormatFlags = 0;
+    switch(mDevice->FmtType)
+    {
+        case DevFmtUByte:
+            mDevice->FmtType = DevFmtByte;
+            /* fall-through */
+        case DevFmtByte:
+            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mBitsPerChannel = 8;
+            break;
+        case DevFmtUShort:
+            mDevice->FmtType = DevFmtShort;
+            /* fall-through */
+        case DevFmtShort:
+            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mBitsPerChannel = 16;
+            break;
+        case DevFmtUInt:
+            mDevice->FmtType = DevFmtInt;
+            /* fall-through */
+        case DevFmtInt:
+            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+            streamFormat.mBitsPerChannel = 32;
+            break;
+        case DevFmtFloat:
+            streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
+            streamFormat.mBitsPerChannel = 32;
+            break;
+    }
+    streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
+                                  streamFormat.mBitsPerChannel / 8;
+    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
+    streamFormat.mFormatID = kAudioFormatLinearPCM;
+    streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
+                                 kLinearPCMFormatFlagIsPacked;
+
+    err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
+        0, &streamFormat, sizeof(AudioStreamBasicDescription));
+    if(err != noErr)
+    {
+        ERR("AudioUnitSetProperty failed\n");
+        return false;
+    }
+
+    /* setup callback */
+    mFrameSize = mDevice->frameSizeFromFmt();
+    AURenderCallbackStruct input{};
+    input.inputProc = CoreAudioPlayback::MixerProcC;
+    input.inputProcRefCon = this;
+
+    err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
+        kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
+    if(err != noErr)
+    {
+        ERR("AudioUnitSetProperty failed\n");
+        return false;
+    }
+
+    /* init the default audio unit... */
+    err = AudioUnitInitialize(mAudioUnit);
+    if(err != noErr)
+    {
+        ERR("AudioUnitInitialize failed\n");
+        return false;
+    }
+
+    return true;
+}
+
+void CoreAudioPlayback::start()
+{
+    const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "AudioOutputUnitStart failed: %d", err};
+}
+
+void CoreAudioPlayback::stop()
+{
+    OSStatus err{AudioOutputUnitStop(mAudioUnit)};
+    if(err != noErr)
+        ERR("AudioOutputUnitStop failed\n");
+}
+
+
+struct CoreAudioCapture final : public BackendBase {
+    CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~CoreAudioCapture() override;
+
+    OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
+        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
+        UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
+    static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
+        const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
+        AudioBufferList *ioData) noexcept
+    {
+        return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
+            inBusNumber, inNumberFrames, ioData);
+    }
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    AudioUnit mAudioUnit{0};
+
+    uint mFrameSize{0u};
+    AudioStreamBasicDescription mFormat{};  // This is the OpenAL format as a CoreAudio ASBD
+
+    SampleConverterPtr mConverter;
+
+    RingBufferPtr mRing{nullptr};
+
+    DEF_NEWDEL(CoreAudioCapture)
+};
+
+CoreAudioCapture::~CoreAudioCapture()
+{
+    if(mAudioUnit)
+        AudioComponentInstanceDispose(mAudioUnit);
+    mAudioUnit = 0;
+}
+
+
+OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
+    const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
+    AudioBufferList*) noexcept
+{
+    AudioUnitRenderActionFlags flags = 0;
+    union {
+        al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
+        AudioBufferList list;
+    } audiobuf{};
+
+    auto rec_vec = mRing->getWriteVector();
+    inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
+        rec_vec.first.len+rec_vec.second.len));
+
+    // Fill the ringbuffer's two segments with data from the input device
+    if(rec_vec.first.len >= inNumberFrames)
+    {
+        audiobuf.list.mNumberBuffers = 1;
+        audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
+        audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
+        audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
+    }
+    else
+    {
+        const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
+        audiobuf.list.mNumberBuffers = 2;
+        audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
+        audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
+        audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
+            mFormat.mBytesPerFrame;
+        audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
+        audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
+        audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
+    }
+    OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
+        inNumberFrames, &audiobuf.list)};
+    if(err != noErr)
+    {
+        ERR("AudioUnitRender error: %d\n", err);
+        return err;
+    }
+
+    mRing->writeAdvance(inNumberFrames);
+    return noErr;
+}
+
+
+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(!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};
+
+    desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IOS
+    desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#else
+    desc.componentSubType = kAudioUnitSubType_HALOutput;
+#endif
+    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+    desc.componentFlags = 0;
+    desc.componentFlagsMask = 0;
+
+    // Search for component with given description
+    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);
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::NoDevice,
+            "Could not create component instance: %u", err};
+
+    // Turn off AudioUnit output
+    enableIO = 0;
+    err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
+        kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO));
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not disable audio unit output property: %u", err};
+
+    // Turn on AudioUnit input
+    enableIO = 1;
+    err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
+        kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO));
+    if(err != noErr)
+        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
+    input.inputProc = CoreAudioCapture::RecordProcC;
+    input.inputProcRefCon = this;
+
+    err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
+        kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not set capture callback: %u", err};
+
+    // Disable buffer allocation for capture
+    UInt32 flag{0};
+    err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
+        kAudioUnitScope_Output, 1, &flag, sizeof(flag));
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not disable buffer allocation property: %u", err};
+
+    // Initialize the device
+    err = AudioUnitInitialize(mAudioUnit);
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not initialize audio unit: %u", err};
+
+    // Get the hardware format
+    propertySize = sizeof(AudioStreamBasicDescription);
+    err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
+        1, &hardwareFormat, &propertySize);
+    if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not get input format: %u", err};
+
+    // Set up the requested format description
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        requestedFormat.mBitsPerChannel = 8;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtUByte:
+        requestedFormat.mBitsPerChannel = 8;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtShort:
+        requestedFormat.mBitsPerChannel = 16;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtUShort:
+        requestedFormat.mBitsPerChannel = 16;
+        requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtInt:
+        requestedFormat.mBitsPerChannel = 32;
+        requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtUInt:
+        requestedFormat.mBitsPerChannel = 32;
+        requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        break;
+    case DevFmtFloat:
+        requestedFormat.mBitsPerChannel = 32;
+        requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+        break;
+    }
+
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        requestedFormat.mChannelsPerFrame = 1;
+        break;
+    case DevFmtStereo:
+        requestedFormat.mChannelsPerFrame = 2;
+        break;
+
+    case DevFmtQuad:
+    case DevFmtX51:
+    case DevFmtX51Rear:
+    case DevFmtX61:
+    case DevFmtX71:
+    case DevFmtAmbi3D:
+        throw al::backend_exception{al::backend_error::DeviceError, "%s not supported",
+            DevFmtChannelsString(mDevice->FmtChans)};
+    }
+
+    requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
+    requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
+    requestedFormat.mSampleRate = mDevice->Frequency;
+    requestedFormat.mFormatID = kAudioFormatLinearPCM;
+    requestedFormat.mReserved = 0;
+    requestedFormat.mFramesPerPacket = 1;
+
+    // save requested format description for later use
+    mFormat = requestedFormat;
+    mFrameSize = mDevice->frameSizeFromFmt();
+
+    // Use intermediate format for sample rate conversion (outputFormat)
+    // Set sample rate to the same as hardware for resampling later
+    outputFormat = requestedFormat;
+    outputFormat.mSampleRate = hardwareFormat.mSampleRate;
+
+    // The output format should be the requested format, but using the hardware sample rate
+    // This is because the AudioUnit will automatically scale other properties, except for sample rate
+    err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
+        1, &outputFormat, sizeof(outputFormat));
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not set input format: %u", err};
+
+    /* Calculate the minimum AudioUnit output format frame count for the pre-
+     * conversion ring buffer. Ensure at least 100ms for the total buffer.
+     */
+    double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency};
+    auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
+        static_cast<UInt32>(outputFormat.mSampleRate)/10);
+    FrameCount64 += MaxResamplerPadding;
+    if(FrameCount64 > std::numeric_limits<int32_t>::max())
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Calculated frame count is too large: %" PRIu64, FrameCount64};
+
+    UInt32 outputFrameCount{};
+    propertySize = sizeof(outputFrameCount);
+    err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
+        kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize);
+    if(err != noErr || propertySize != sizeof(outputFrameCount))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Could not get input frame count: %u", err};
+
+    outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
+    mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
+
+    /* Set up sample converter if needed */
+    if(outputFormat.mSampleRate != mDevice->Frequency)
+        mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
+            mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
+            mDevice->Frequency, Resampler::FastBSinc24);
+
+    mDevice->DeviceName = name;
+}
+
+
+void CoreAudioCapture::start()
+{
+    OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+    if(err != noErr)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "AudioOutputUnitStart failed: %d", err};
+}
+
+void CoreAudioCapture::stop()
+{
+    OSStatus err{AudioOutputUnitStop(mAudioUnit)};
+    if(err != noErr)
+        ERR("AudioOutputUnitStop failed\n");
+}
+
+void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
+{
+    if(!mConverter)
+    {
+        mRing->read(buffer, samples);
+        return;
+    }
+
+    auto rec_vec = mRing->getReadVector();
+    const void *src0{rec_vec.first.buf};
+    auto src0len = static_cast<uint>(rec_vec.first.len);
+    uint got{mConverter->convert(&src0, &src0len, buffer, samples)};
+    size_t total_read{rec_vec.first.len - src0len};
+    if(got < samples && !src0len && rec_vec.second.len > 0)
+    {
+        const void *src1{rec_vec.second.buf};
+        auto src1len = static_cast<uint>(rec_vec.second.len);
+        got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
+        total_read += rec_vec.second.len - src1len;
+    }
+
+    mRing->readAdvance(total_read);
+}
+
+uint CoreAudioCapture::availableSamples()
+{
+    if(!mConverter) return static_cast<uint>(mRing->readSpace());
+    return mConverter->availableOut(static_cast<uint>(mRing->readSpace()));
+}
+
+} // namespace
+
+BackendFactory &CoreAudioBackendFactory::getFactory()
+{
+    static CoreAudioBackendFactory factory{};
+    return factory;
+}
+
+bool CoreAudioBackendFactory::init() { return true; }
+
+bool CoreAudioBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string CoreAudioBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+    case BackendType::Capture:
+        /* Includes null char. */
+        outnames.append(ca_device, sizeof(ca_device));
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new CoreAudioPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new CoreAudioCapture{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/coreaudio.h

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

+ 0 - 1078
Engine/lib/openal-soft/Alc/backends/dsound.c

@@ -1,1078 +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 <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
-#include <dsound.h>
-#include <cguid.h>
-#include <mmreg.h>
-#ifndef _WAVEFORMATEXTENSIBLE_
-#include <ks.h>
-#include <ksmedia.h>
-#endif
-
-#include "alMain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-#include "alstring.h"
-
-#include "backends/base.h"
-
-#ifndef DSSPEAKER_5POINT1
-#   define DSSPEAKER_5POINT1          0x00000006
-#endif
-#ifndef DSSPEAKER_5POINT1_BACK
-#   define DSSPEAKER_5POINT1_BACK     0x00000006
-#endif
-#ifndef DSSPEAKER_7POINT1
-#   define DSSPEAKER_7POINT1          0x00000007
-#endif
-#ifndef DSSPEAKER_7POINT1_SURROUND
-#   define DSSPEAKER_7POINT1_SURROUND 0x00000008
-#endif
-#ifndef DSSPEAKER_5POINT1_SURROUND
-#   define DSSPEAKER_5POINT1_SURROUND 0x00000009
-#endif
-
-
-DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-
-#define DEVNAME_HEAD "OpenAL Soft on "
-
-
-#ifdef HAVE_DYNLOAD
-static void *ds_handle;
-static HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
-static HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
-static HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
-static HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
-
-#define DirectSoundCreate            pDirectSoundCreate
-#define DirectSoundEnumerateW        pDirectSoundEnumerateW
-#define DirectSoundCaptureCreate     pDirectSoundCaptureCreate
-#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
-#endif
-
-
-static ALCboolean DSoundLoad(void)
-{
-#ifdef HAVE_DYNLOAD
-    if(!ds_handle)
-    {
-        ds_handle = LoadLib("dsound.dll");
-        if(ds_handle == NULL)
-        {
-            ERR("Failed to load dsound.dll\n");
-            return ALC_FALSE;
-        }
-
-#define LOAD_FUNC(f) do {                                                     \
-    p##f = GetSymbol(ds_handle, #f);                                          \
-    if(p##f == NULL) {                                                        \
-        CloseLib(ds_handle);                                                  \
-        ds_handle = NULL;                                                     \
-        return ALC_FALSE;                                                     \
-    }                                                                         \
-} while(0)
-        LOAD_FUNC(DirectSoundCreate);
-        LOAD_FUNC(DirectSoundEnumerateW);
-        LOAD_FUNC(DirectSoundCaptureCreate);
-        LOAD_FUNC(DirectSoundCaptureEnumerateW);
-#undef LOAD_FUNC
-    }
-#endif
-    return ALC_TRUE;
-}
-
-
-#define MAX_UPDATES 128
-
-typedef struct {
-    al_string name;
-    GUID guid;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
-
-static vector_DevMap PlaybackDevices;
-static vector_DevMap CaptureDevices;
-
-static void clear_devlist(vector_DevMap *list)
-{
-#define DEINIT_STR(i) AL_STRING_DEINIT((i)->name)
-    VECTOR_FOR_EACH(DevMap, *list, DEINIT_STR);
-    VECTOR_RESIZE(*list, 0, 0);
-#undef DEINIT_STR
-}
-
-static BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR* UNUSED(drvname), void *data)
-{
-    vector_DevMap *devices = data;
-    OLECHAR *guidstr = NULL;
-    DevMap entry;
-    HRESULT hr;
-    int count;
-
-    if(!guid)
-        return TRUE;
-
-    AL_STRING_INIT(entry.name);
-
-    count = 0;
-    while(1)
-    {
-        const DevMap *iter;
-
-        alstr_copy_cstr(&entry.name, DEVNAME_HEAD);
-        alstr_append_wcstr(&entry.name, desc);
-        if(count != 0)
-        {
-            char str[64];
-            snprintf(str, sizeof(str), " #%d", count+1);
-            alstr_append_cstr(&entry.name, str);
-        }
-
-#define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, *devices, MATCH_ENTRY);
-        if(iter == VECTOR_END(*devices)) break;
-#undef MATCH_ENTRY
-        count++;
-    }
-    entry.guid = *guid;
-
-    hr = StringFromCLSID(guid, &guidstr);
-    if(SUCCEEDED(hr))
-    {
-        TRACE("Got device \"%s\", GUID \"%ls\"\n", alstr_get_cstr(entry.name), guidstr);
-        CoTaskMemFree(guidstr);
-    }
-
-    VECTOR_PUSH_BACK(*devices, entry);
-
-    return TRUE;
-}
-
-
-typedef struct ALCdsoundPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    IDirectSound       *DS;
-    IDirectSoundBuffer *PrimaryBuffer;
-    IDirectSoundBuffer *Buffer;
-    IDirectSoundNotify *Notifies;
-    HANDLE             NotifyEvent;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCdsoundPlayback;
-
-static int ALCdsoundPlayback_mixerProc(void *ptr);
-
-static void ALCdsoundPlayback_Construct(ALCdsoundPlayback *self, ALCdevice *device);
-static void ALCdsoundPlayback_Destruct(ALCdsoundPlayback *self);
-static ALCenum ALCdsoundPlayback_open(ALCdsoundPlayback *self, const ALCchar *name);
-static ALCboolean ALCdsoundPlayback_reset(ALCdsoundPlayback *self);
-static ALCboolean ALCdsoundPlayback_start(ALCdsoundPlayback *self);
-static void ALCdsoundPlayback_stop(ALCdsoundPlayback *self);
-static DECLARE_FORWARD2(ALCdsoundPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCdsoundPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCdsoundPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCdsoundPlayback);
-
-
-static void ALCdsoundPlayback_Construct(ALCdsoundPlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCdsoundPlayback, ALCbackend, self);
-
-    self->DS = NULL;
-    self->PrimaryBuffer = NULL;
-    self->Buffer = NULL;
-    self->Notifies = NULL;
-    self->NotifyEvent = NULL;
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCdsoundPlayback_Destruct(ALCdsoundPlayback *self)
-{
-    if(self->Notifies)
-        IDirectSoundNotify_Release(self->Notifies);
-    self->Notifies = NULL;
-    if(self->Buffer)
-        IDirectSoundBuffer_Release(self->Buffer);
-    self->Buffer = NULL;
-    if(self->PrimaryBuffer != NULL)
-        IDirectSoundBuffer_Release(self->PrimaryBuffer);
-    self->PrimaryBuffer = NULL;
-
-    if(self->DS)
-        IDirectSound_Release(self->DS);
-    self->DS = NULL;
-    if(self->NotifyEvent)
-        CloseHandle(self->NotifyEvent);
-    self->NotifyEvent = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-FORCE_ALIGN static int ALCdsoundPlayback_mixerProc(void *ptr)
-{
-    ALCdsoundPlayback *self = ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    DSBCAPS DSBCaps;
-    DWORD LastCursor = 0;
-    DWORD PlayCursor;
-    void *WritePtr1, *WritePtr2;
-    DWORD WriteCnt1,  WriteCnt2;
-    BOOL Playing = FALSE;
-    DWORD FrameSize;
-    DWORD FragSize;
-    DWORD avail;
-    HRESULT err;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    memset(&DSBCaps, 0, sizeof(DSBCaps));
-    DSBCaps.dwSize = sizeof(DSBCaps);
-    err = IDirectSoundBuffer_GetCaps(self->Buffer, &DSBCaps);
-    if(FAILED(err))
-    {
-        ERR("Failed to get buffer caps: 0x%lx\n", err);
-        ALCdevice_Lock(device);
-        aluHandleDisconnect(device, "Failure retrieving playback buffer info: 0x%lx", err);
-        ALCdevice_Unlock(device);
-        return 1;
-    }
-
-    FrameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    FragSize = device->UpdateSize * FrameSize;
-
-    IDirectSoundBuffer_GetCurrentPosition(self->Buffer, &LastCursor, NULL);
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        // Get current play cursor
-        IDirectSoundBuffer_GetCurrentPosition(self->Buffer, &PlayCursor, NULL);
-        avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
-
-        if(avail < FragSize)
-        {
-            if(!Playing)
-            {
-                err = IDirectSoundBuffer_Play(self->Buffer, 0, 0, DSBPLAY_LOOPING);
-                if(FAILED(err))
-                {
-                    ERR("Failed to play buffer: 0x%lx\n", err);
-                    ALCdevice_Lock(device);
-                    aluHandleDisconnect(device, "Failure starting playback: 0x%lx", err);
-                    ALCdevice_Unlock(device);
-                    return 1;
-                }
-                Playing = TRUE;
-            }
-
-            avail = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
-            if(avail != WAIT_OBJECT_0)
-                ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
-            continue;
-        }
-        avail -= avail%FragSize;
-
-        // Lock output buffer
-        WriteCnt1 = 0;
-        WriteCnt2 = 0;
-        err = IDirectSoundBuffer_Lock(self->Buffer, LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
-
-        // If the buffer is lost, restore it and lock
-        if(err == DSERR_BUFFERLOST)
-        {
-            WARN("Buffer lost, restoring...\n");
-            err = IDirectSoundBuffer_Restore(self->Buffer);
-            if(SUCCEEDED(err))
-            {
-                Playing = FALSE;
-                LastCursor = 0;
-                err = IDirectSoundBuffer_Lock(self->Buffer, 0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
-            }
-        }
-
-        // Successfully locked the output buffer
-        if(SUCCEEDED(err))
-        {
-            // If we have an active context, mix data directly into output buffer otherwise fill with silence
-            ALCdevice_Lock(device);
-            aluMixData(device, WritePtr1, WriteCnt1/FrameSize);
-            aluMixData(device, WritePtr2, WriteCnt2/FrameSize);
-            ALCdevice_Unlock(device);
-
-            // Unlock output buffer only when successfully locked
-            IDirectSoundBuffer_Unlock(self->Buffer, WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
-        }
-        else
-        {
-            ERR("Buffer lock error: %#lx\n", err);
-            ALCdevice_Lock(device);
-            aluHandleDisconnect(device, "Failed to lock output buffer: 0x%lx", err);
-            ALCdevice_Unlock(device);
-            return 1;
-        }
-
-        // Update old write cursor location
-        LastCursor += WriteCnt1+WriteCnt2;
-        LastCursor %= DSBCaps.dwBufferBytes;
-    }
-
-    return 0;
-}
-
-static ALCenum ALCdsoundPlayback_open(ALCdsoundPlayback *self, const ALCchar *deviceName)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const GUID *guid = NULL;
-    HRESULT hr, hrcom;
-
-    if(VECTOR_SIZE(PlaybackDevices) == 0)
-    {
-        /* Initialize COM to prevent name truncation */
-        hrcom = CoInitialize(NULL);
-        hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
-        if(FAILED(hr))
-            ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
-        if(SUCCEEDED(hrcom))
-            CoUninitialize();
-    }
-
-    if(!deviceName && VECTOR_SIZE(PlaybackDevices) > 0)
-    {
-        deviceName = alstr_get_cstr(VECTOR_FRONT(PlaybackDevices).name);
-        guid = &VECTOR_FRONT(PlaybackDevices).guid;
-    }
-    else
-    {
-        const DevMap *iter;
-
-#define MATCH_NAME(i)  (alstr_cmp_cstr((i)->name, deviceName) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(PlaybackDevices))
-            return ALC_INVALID_VALUE;
-        guid = &iter->guid;
-    }
-
-    hr = DS_OK;
-    self->NotifyEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-    if(self->NotifyEvent == NULL)
-        hr = E_FAIL;
-
-    //DirectSound Init code
-    if(SUCCEEDED(hr))
-        hr = DirectSoundCreate(guid, &self->DS, NULL);
-    if(SUCCEEDED(hr))
-        hr = IDirectSound_SetCooperativeLevel(self->DS, GetForegroundWindow(), DSSCL_PRIORITY);
-    if(FAILED(hr))
-    {
-        if(self->DS)
-            IDirectSound_Release(self->DS);
-        self->DS = NULL;
-        if(self->NotifyEvent)
-            CloseHandle(self->NotifyEvent);
-        self->NotifyEvent = NULL;
-
-        ERR("Device init failed: 0x%08lx\n", hr);
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, deviceName);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCdsoundPlayback_reset(ALCdsoundPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    DSBUFFERDESC DSBDescription;
-    WAVEFORMATEXTENSIBLE OutputType;
-    DWORD speakers;
-    HRESULT hr;
-
-    memset(&OutputType, 0, sizeof(OutputType));
-
-    if(self->Notifies)
-        IDirectSoundNotify_Release(self->Notifies);
-    self->Notifies = NULL;
-    if(self->Buffer)
-        IDirectSoundBuffer_Release(self->Buffer);
-    self->Buffer = NULL;
-    if(self->PrimaryBuffer != NULL)
-        IDirectSoundBuffer_Release(self->PrimaryBuffer);
-    self->PrimaryBuffer = NULL;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            device->FmtType = DevFmtUByte;
-            break;
-        case DevFmtFloat:
-            if((device->Flags&DEVICE_SAMPLE_TYPE_REQUEST))
-                break;
-            /* fall-through */
-        case DevFmtUShort:
-            device->FmtType = DevFmtShort;
-            break;
-        case DevFmtUInt:
-            device->FmtType = DevFmtInt;
-            break;
-        case DevFmtUByte:
-        case DevFmtShort:
-        case DevFmtInt:
-            break;
-    }
-
-    hr = IDirectSound_GetSpeakerConfig(self->DS, &speakers);
-    if(SUCCEEDED(hr))
-    {
-        speakers = DSSPEAKER_CONFIG(speakers);
-        if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
-        {
-            if(speakers == DSSPEAKER_MONO)
-                device->FmtChans = DevFmtMono;
-            else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
-                device->FmtChans = DevFmtStereo;
-            else if(speakers == DSSPEAKER_QUAD)
-                device->FmtChans = DevFmtQuad;
-            else if(speakers == DSSPEAKER_5POINT1_SURROUND)
-                device->FmtChans = DevFmtX51;
-            else if(speakers == DSSPEAKER_5POINT1_BACK)
-                device->FmtChans = DevFmtX51Rear;
-            else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
-                device->FmtChans = DevFmtX71;
-            else
-                ERR("Unknown system speaker config: 0x%lx\n", speakers);
-        }
-        device->IsHeadphones = (device->FmtChans == DevFmtStereo &&
-                                speakers == DSSPEAKER_HEADPHONE);
-
-        switch(device->FmtChans)
-        {
-            case DevFmtMono:
-                OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
-                break;
-            case DevFmtAmbi3D:
-                device->FmtChans = DevFmtStereo;
-                /*fall-through*/
-            case DevFmtStereo:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT;
-                break;
-            case DevFmtQuad:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT |
-                                           SPEAKER_BACK_LEFT |
-                                           SPEAKER_BACK_RIGHT;
-                break;
-            case DevFmtX51:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT |
-                                           SPEAKER_FRONT_CENTER |
-                                           SPEAKER_LOW_FREQUENCY |
-                                           SPEAKER_SIDE_LEFT |
-                                           SPEAKER_SIDE_RIGHT;
-                break;
-            case DevFmtX51Rear:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT |
-                                           SPEAKER_FRONT_CENTER |
-                                           SPEAKER_LOW_FREQUENCY |
-                                           SPEAKER_BACK_LEFT |
-                                           SPEAKER_BACK_RIGHT;
-                break;
-            case DevFmtX61:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT |
-                                           SPEAKER_FRONT_CENTER |
-                                           SPEAKER_LOW_FREQUENCY |
-                                           SPEAKER_BACK_CENTER |
-                                           SPEAKER_SIDE_LEFT |
-                                           SPEAKER_SIDE_RIGHT;
-                break;
-            case DevFmtX71:
-                OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                           SPEAKER_FRONT_RIGHT |
-                                           SPEAKER_FRONT_CENTER |
-                                           SPEAKER_LOW_FREQUENCY |
-                                           SPEAKER_BACK_LEFT |
-                                           SPEAKER_BACK_RIGHT |
-                                           SPEAKER_SIDE_LEFT |
-                                           SPEAKER_SIDE_RIGHT;
-                break;
-        }
-
-retry_open:
-        hr = S_OK;
-        OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
-        OutputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-        OutputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-        OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8;
-        OutputType.Format.nSamplesPerSec = device->Frequency;
-        OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign;
-        OutputType.Format.cbSize = 0;
-    }
-
-    if(OutputType.Format.nChannels > 2 || device->FmtType == DevFmtFloat)
-    {
-        OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-        OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
-        OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
-        if(device->FmtType == DevFmtFloat)
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-        else
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-
-        if(self->PrimaryBuffer)
-            IDirectSoundBuffer_Release(self->PrimaryBuffer);
-        self->PrimaryBuffer = NULL;
-    }
-    else
-    {
-        if(SUCCEEDED(hr) && !self->PrimaryBuffer)
-        {
-            memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
-            DSBDescription.dwSize=sizeof(DSBUFFERDESC);
-            DSBDescription.dwFlags=DSBCAPS_PRIMARYBUFFER;
-            hr = IDirectSound_CreateSoundBuffer(self->DS, &DSBDescription, &self->PrimaryBuffer, NULL);
-        }
-        if(SUCCEEDED(hr))
-            hr = IDirectSoundBuffer_SetFormat(self->PrimaryBuffer,&OutputType.Format);
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        if(device->NumUpdates > MAX_UPDATES)
-        {
-            device->UpdateSize = (device->UpdateSize*device->NumUpdates +
-                                  MAX_UPDATES-1) / MAX_UPDATES;
-            device->NumUpdates = MAX_UPDATES;
-        }
-
-        memset(&DSBDescription,0,sizeof(DSBUFFERDESC));
-        DSBDescription.dwSize=sizeof(DSBUFFERDESC);
-        DSBDescription.dwFlags=DSBCAPS_CTRLPOSITIONNOTIFY|DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_GLOBALFOCUS;
-        DSBDescription.dwBufferBytes=device->UpdateSize * device->NumUpdates *
-                                     OutputType.Format.nBlockAlign;
-        DSBDescription.lpwfxFormat=&OutputType.Format;
-        hr = IDirectSound_CreateSoundBuffer(self->DS, &DSBDescription, &self->Buffer, NULL);
-        if(FAILED(hr) && device->FmtType == DevFmtFloat)
-        {
-            device->FmtType = DevFmtShort;
-            goto retry_open;
-        }
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        hr = IDirectSoundBuffer_QueryInterface(self->Buffer, &IID_IDirectSoundNotify, (void**)&self->Notifies);
-        if(SUCCEEDED(hr))
-        {
-            DSBPOSITIONNOTIFY notifies[MAX_UPDATES];
-            ALuint i;
-
-            for(i = 0;i < device->NumUpdates;++i)
-            {
-                notifies[i].dwOffset = i * device->UpdateSize *
-                                       OutputType.Format.nBlockAlign;
-                notifies[i].hEventNotify = self->NotifyEvent;
-            }
-            if(IDirectSoundNotify_SetNotificationPositions(self->Notifies, device->NumUpdates, notifies) != DS_OK)
-                hr = E_FAIL;
-        }
-    }
-
-    if(FAILED(hr))
-    {
-        if(self->Notifies != NULL)
-            IDirectSoundNotify_Release(self->Notifies);
-        self->Notifies = NULL;
-        if(self->Buffer != NULL)
-            IDirectSoundBuffer_Release(self->Buffer);
-        self->Buffer = NULL;
-        if(self->PrimaryBuffer != NULL)
-            IDirectSoundBuffer_Release(self->PrimaryBuffer);
-        self->PrimaryBuffer = NULL;
-        return ALC_FALSE;
-    }
-
-    ResetEvent(self->NotifyEvent);
-    SetDefaultWFXChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCdsoundPlayback_start(ALCdsoundPlayback *self)
-{
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCdsoundPlayback_mixerProc, self) != althrd_success)
-        return ALC_FALSE;
-
-    return ALC_TRUE;
-}
-
-static void ALCdsoundPlayback_stop(ALCdsoundPlayback *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &res);
-
-    IDirectSoundBuffer_Stop(self->Buffer);
-}
-
-
-
-typedef struct ALCdsoundCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    IDirectSoundCapture *DSC;
-    IDirectSoundCaptureBuffer *DSCbuffer;
-    DWORD BufferBytes;
-    DWORD Cursor;
-
-    ll_ringbuffer_t *Ring;
-} ALCdsoundCapture;
-
-static void ALCdsoundCapture_Construct(ALCdsoundCapture *self, ALCdevice *device);
-static void ALCdsoundCapture_Destruct(ALCdsoundCapture *self);
-static ALCenum ALCdsoundCapture_open(ALCdsoundCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCdsoundCapture_start(ALCdsoundCapture *self);
-static void ALCdsoundCapture_stop(ALCdsoundCapture *self);
-static ALCenum ALCdsoundCapture_captureSamples(ALCdsoundCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCdsoundCapture_availableSamples(ALCdsoundCapture *self);
-static DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCdsoundCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCdsoundCapture)
-
-DEFINE_ALCBACKEND_VTABLE(ALCdsoundCapture);
-
-static void ALCdsoundCapture_Construct(ALCdsoundCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCdsoundCapture, ALCbackend, self);
-
-    self->DSC = NULL;
-    self->DSCbuffer = NULL;
-    self->Ring = NULL;
-}
-
-static void ALCdsoundCapture_Destruct(ALCdsoundCapture *self)
-{
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = NULL;
-
-    if(self->DSCbuffer != NULL)
-    {
-        IDirectSoundCaptureBuffer_Stop(self->DSCbuffer);
-        IDirectSoundCaptureBuffer_Release(self->DSCbuffer);
-        self->DSCbuffer = NULL;
-    }
-
-    if(self->DSC)
-        IDirectSoundCapture_Release(self->DSC);
-    self->DSC = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static ALCenum ALCdsoundCapture_open(ALCdsoundCapture *self, const ALCchar *deviceName)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    WAVEFORMATEXTENSIBLE InputType;
-    DSCBUFFERDESC DSCBDescription;
-    const GUID *guid = NULL;
-    HRESULT hr, hrcom;
-    ALuint samples;
-
-    if(VECTOR_SIZE(CaptureDevices) == 0)
-    {
-        /* Initialize COM to prevent name truncation */
-        hrcom = CoInitialize(NULL);
-        hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
-        if(FAILED(hr))
-            ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
-        if(SUCCEEDED(hrcom))
-            CoUninitialize();
-    }
-
-    if(!deviceName && VECTOR_SIZE(CaptureDevices) > 0)
-    {
-        deviceName = alstr_get_cstr(VECTOR_FRONT(CaptureDevices).name);
-        guid = &VECTOR_FRONT(CaptureDevices).guid;
-    }
-    else
-    {
-        const DevMap *iter;
-
-#define MATCH_NAME(i)  (alstr_cmp_cstr((i)->name, deviceName) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(CaptureDevices))
-            return ALC_INVALID_VALUE;
-        guid = &iter->guid;
-    }
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-        case DevFmtUShort:
-        case DevFmtUInt:
-            WARN("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
-            return ALC_INVALID_ENUM;
-
-        case DevFmtUByte:
-        case DevFmtShort:
-        case DevFmtInt:
-        case DevFmtFloat:
-            break;
-    }
-
-    memset(&InputType, 0, sizeof(InputType));
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
-            break;
-        case DevFmtStereo:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT;
-            break;
-        case DevFmtQuad:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT |
-                                      SPEAKER_BACK_LEFT |
-                                      SPEAKER_BACK_RIGHT;
-            break;
-        case DevFmtX51:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT |
-                                      SPEAKER_FRONT_CENTER |
-                                      SPEAKER_LOW_FREQUENCY |
-                                      SPEAKER_SIDE_LEFT |
-                                      SPEAKER_SIDE_RIGHT;
-            break;
-        case DevFmtX51Rear:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT |
-                                      SPEAKER_FRONT_CENTER |
-                                      SPEAKER_LOW_FREQUENCY |
-                                      SPEAKER_BACK_LEFT |
-                                      SPEAKER_BACK_RIGHT;
-            break;
-        case DevFmtX61:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT |
-                                      SPEAKER_FRONT_CENTER |
-                                      SPEAKER_LOW_FREQUENCY |
-                                      SPEAKER_BACK_CENTER |
-                                      SPEAKER_SIDE_LEFT |
-                                      SPEAKER_SIDE_RIGHT;
-            break;
-        case DevFmtX71:
-            InputType.dwChannelMask = SPEAKER_FRONT_LEFT |
-                                      SPEAKER_FRONT_RIGHT |
-                                      SPEAKER_FRONT_CENTER |
-                                      SPEAKER_LOW_FREQUENCY |
-                                      SPEAKER_BACK_LEFT |
-                                      SPEAKER_BACK_RIGHT |
-                                      SPEAKER_SIDE_LEFT |
-                                      SPEAKER_SIDE_RIGHT;
-            break;
-        case DevFmtAmbi3D:
-            WARN("%s capture not supported\n", DevFmtChannelsString(device->FmtChans));
-            return ALC_INVALID_ENUM;
-    }
-
-    InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
-    InputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    InputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    InputType.Format.nBlockAlign = InputType.Format.nChannels*InputType.Format.wBitsPerSample/8;
-    InputType.Format.nSamplesPerSec = device->Frequency;
-    InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec*InputType.Format.nBlockAlign;
-    InputType.Format.cbSize = 0;
-    InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
-    if(device->FmtType == DevFmtFloat)
-        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-    else
-        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-
-    if(InputType.Format.nChannels > 2 || device->FmtType == DevFmtFloat)
-    {
-        InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-        InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
-    }
-
-    samples = device->UpdateSize * device->NumUpdates;
-    samples = maxu(samples, 100 * device->Frequency / 1000);
-
-    memset(&DSCBDescription, 0, sizeof(DSCBUFFERDESC));
-    DSCBDescription.dwSize = sizeof(DSCBUFFERDESC);
-    DSCBDescription.dwFlags = 0;
-    DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
-    DSCBDescription.lpwfxFormat = &InputType.Format;
-
-    //DirectSoundCapture Init code
-    hr = DirectSoundCaptureCreate(guid, &self->DSC, NULL);
-    if(SUCCEEDED(hr))
-        hr = IDirectSoundCapture_CreateCaptureBuffer(self->DSC, &DSCBDescription, &self->DSCbuffer, NULL);
-    if(SUCCEEDED(hr))
-    {
-         self->Ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates,
-                                           InputType.Format.nBlockAlign, false);
-         if(self->Ring == NULL)
-             hr = DSERR_OUTOFMEMORY;
-    }
-
-    if(FAILED(hr))
-    {
-        ERR("Device init failed: 0x%08lx\n", hr);
-
-        ll_ringbuffer_free(self->Ring);
-        self->Ring = NULL;
-        if(self->DSCbuffer != NULL)
-            IDirectSoundCaptureBuffer_Release(self->DSCbuffer);
-        self->DSCbuffer = NULL;
-        if(self->DSC)
-            IDirectSoundCapture_Release(self->DSC);
-        self->DSC = NULL;
-
-        return ALC_INVALID_VALUE;
-    }
-
-    self->BufferBytes = DSCBDescription.dwBufferBytes;
-    SetDefaultWFXChannelOrder(device);
-
-    alstr_copy_cstr(&device->DeviceName, deviceName);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCdsoundCapture_start(ALCdsoundCapture *self)
-{
-    HRESULT hr;
-
-    hr = IDirectSoundCaptureBuffer_Start(self->DSCbuffer, DSCBSTART_LOOPING);
-    if(FAILED(hr))
-    {
-        ERR("start failed: 0x%08lx\n", hr);
-        aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice,
-                            "Failure starting capture: 0x%lx", hr);
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCdsoundCapture_stop(ALCdsoundCapture *self)
-{
-    HRESULT hr;
-
-    hr = IDirectSoundCaptureBuffer_Stop(self->DSCbuffer);
-    if(FAILED(hr))
-    {
-        ERR("stop failed: 0x%08lx\n", hr);
-        aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice,
-                            "Failure stopping capture: 0x%lx", hr);
-    }
-}
-
-static ALCenum ALCdsoundCapture_captureSamples(ALCdsoundCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    ll_ringbuffer_read(self->Ring, buffer, samples);
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCdsoundCapture_availableSamples(ALCdsoundCapture *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    DWORD ReadCursor, LastCursor, BufferBytes, NumBytes;
-    void *ReadPtr1, *ReadPtr2;
-    DWORD ReadCnt1,  ReadCnt2;
-    DWORD FrameSize;
-    HRESULT hr;
-
-    if(!ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-        goto done;
-
-    FrameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    BufferBytes = self->BufferBytes;
-    LastCursor = self->Cursor;
-
-    hr = IDirectSoundCaptureBuffer_GetCurrentPosition(self->DSCbuffer, NULL, &ReadCursor);
-    if(SUCCEEDED(hr))
-    {
-        NumBytes = (ReadCursor-LastCursor + BufferBytes) % BufferBytes;
-        if(NumBytes == 0)
-            goto done;
-        hr = IDirectSoundCaptureBuffer_Lock(self->DSCbuffer, LastCursor, NumBytes,
-                                            &ReadPtr1, &ReadCnt1,
-                                            &ReadPtr2, &ReadCnt2, 0);
-    }
-    if(SUCCEEDED(hr))
-    {
-        ll_ringbuffer_write(self->Ring, ReadPtr1, ReadCnt1/FrameSize);
-        if(ReadPtr2 != NULL)
-            ll_ringbuffer_write(self->Ring, ReadPtr2, ReadCnt2/FrameSize);
-        hr = IDirectSoundCaptureBuffer_Unlock(self->DSCbuffer,
-                                              ReadPtr1, ReadCnt1,
-                                              ReadPtr2, ReadCnt2);
-        self->Cursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
-    }
-
-    if(FAILED(hr))
-    {
-        ERR("update failed: 0x%08lx\n", hr);
-        aluHandleDisconnect(device, "Failure retrieving capture data: 0x%lx", hr);
-    }
-
-done:
-    return (ALCuint)ll_ringbuffer_read_space(self->Ring);
-}
-
-
-static inline void AppendAllDevicesList2(const DevMap *entry)
-{ AppendAllDevicesList(alstr_get_cstr(entry->name)); }
-static inline void AppendCaptureDeviceList2(const DevMap *entry)
-{ AppendCaptureDeviceList(alstr_get_cstr(entry->name)); }
-
-typedef struct ALCdsoundBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCdsoundBackendFactory;
-#define ALCDSOUNDBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCdsoundBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCdsoundBackendFactory_getFactory(void);
-
-static ALCboolean ALCdsoundBackendFactory_init(ALCdsoundBackendFactory *self);
-static void ALCdsoundBackendFactory_deinit(ALCdsoundBackendFactory *self);
-static ALCboolean ALCdsoundBackendFactory_querySupport(ALCdsoundBackendFactory *self, ALCbackend_Type type);
-static void ALCdsoundBackendFactory_probe(ALCdsoundBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCdsoundBackendFactory_createBackend(ALCdsoundBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCdsoundBackendFactory);
-
-
-ALCbackendFactory *ALCdsoundBackendFactory_getFactory(void)
-{
-    static ALCdsoundBackendFactory factory = ALCDSOUNDBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCdsoundBackendFactory_init(ALCdsoundBackendFactory* UNUSED(self))
-{
-    VECTOR_INIT(PlaybackDevices);
-    VECTOR_INIT(CaptureDevices);
-
-    if(!DSoundLoad())
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCdsoundBackendFactory_deinit(ALCdsoundBackendFactory* UNUSED(self))
-{
-    clear_devlist(&PlaybackDevices);
-    VECTOR_DEINIT(PlaybackDevices);
-
-    clear_devlist(&CaptureDevices);
-    VECTOR_DEINIT(CaptureDevices);
-
-#ifdef HAVE_DYNLOAD
-    if(ds_handle)
-        CloseLib(ds_handle);
-    ds_handle = NULL;
-#endif
-}
-
-static ALCboolean ALCdsoundBackendFactory_querySupport(ALCdsoundBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCdsoundBackendFactory_probe(ALCdsoundBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    HRESULT hr, hrcom;
-
-    /* Initialize COM to prevent name truncation */
-    hrcom = CoInitialize(NULL);
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            clear_devlist(&PlaybackDevices);
-            hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
-            if(FAILED(hr))
-                ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
-            VECTOR_FOR_EACH(const DevMap, PlaybackDevices, AppendAllDevicesList2);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            clear_devlist(&CaptureDevices);
-            hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
-            if(FAILED(hr))
-                ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
-            VECTOR_FOR_EACH(const DevMap, CaptureDevices, AppendCaptureDeviceList2);
-            break;
-    }
-    if(SUCCEEDED(hrcom))
-        CoUninitialize();
-}
-
-static ALCbackend* ALCdsoundBackendFactory_createBackend(ALCdsoundBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCdsoundPlayback *backend;
-        NEW_OBJ(backend, ALCdsoundPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    if(type == ALCbackend_Capture)
-    {
-        ALCdsoundCapture *backend;
-        NEW_OBJ(backend, ALCdsoundCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 868 - 0
Engine/lib/openal-soft/Alc/backends/dsound.cpp

@@ -0,0 +1,868 @@
+/**
+ * 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 "backends/dsound.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#include <cguid.h>
+#include <mmreg.h>
+#ifndef _WAVEFORMATEXTENSIBLE_
+#include <ks.h>
+#include <ksmedia.h>
+#endif
+
+#include <atomic>
+#include <cassert>
+#include <thread>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+
+/* MinGW-w64 needs this for some unknown reason now. */
+using LPCWAVEFORMATEX = const WAVEFORMATEX*;
+#include <dsound.h>
+
+
+#ifndef DSSPEAKER_5POINT1
+#   define DSSPEAKER_5POINT1          0x00000006
+#endif
+#ifndef DSSPEAKER_5POINT1_BACK
+#   define DSSPEAKER_5POINT1_BACK     0x00000006
+#endif
+#ifndef DSSPEAKER_7POINT1
+#   define DSSPEAKER_7POINT1          0x00000007
+#endif
+#ifndef DSSPEAKER_7POINT1_SURROUND
+#   define DSSPEAKER_7POINT1_SURROUND 0x00000008
+#endif
+#ifndef DSSPEAKER_5POINT1_SURROUND
+#   define DSSPEAKER_5POINT1_SURROUND 0x00000009
+#endif
+
+
+/* Some headers seem to define these as macros for __uuidof, which is annoying
+ * since some headers don't declare them at all. Hopefully the ifdef is enough
+ * to tell if they need to be declared.
+ */
+#ifndef KSDATAFORMAT_SUBTYPE_PCM
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+#endif
+#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+#endif
+
+namespace {
+
+#define DEVNAME_HEAD "OpenAL Soft on "
+
+
+#ifdef HAVE_DYNLOAD
+void *ds_handle;
+HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
+HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
+HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
+HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
+
+#ifndef IN_IDE_PARSER
+#define DirectSoundCreate            pDirectSoundCreate
+#define DirectSoundEnumerateW        pDirectSoundEnumerateW
+#define DirectSoundCaptureCreate     pDirectSoundCaptureCreate
+#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
+#endif
+#endif
+
+
+#define MONO SPEAKER_FRONT_CENTER
+#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
+#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+
+#define MAX_UPDATES 128
+
+struct DevMap {
+    std::string name;
+    GUID guid;
+
+    template<typename T0, typename T1>
+    DevMap(T0&& name_, T1&& guid_)
+      : name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
+    { }
+};
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+bool checkName(const al::vector<DevMap> &list, const std::string &name)
+{
+    auto match_name = [&name](const DevMap &entry) -> bool
+    { return entry.name == name; };
+    return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
+}
+
+BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
+{
+    if(!guid)
+        return TRUE;
+
+    auto& devices = *static_cast<al::vector<DevMap>*>(data);
+    const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
+
+    int count{1};
+    std::string newname{basename};
+    while(checkName(devices, newname))
+    {
+        newname = basename;
+        newname += " #";
+        newname += std::to_string(++count);
+    }
+    devices.emplace_back(std::move(newname), *guid);
+    const DevMap &newentry = devices.back();
+
+    OLECHAR *guidstr{nullptr};
+    HRESULT hr{StringFromCLSID(*guid, &guidstr)};
+    if(SUCCEEDED(hr))
+    {
+        TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
+        CoTaskMemFree(guidstr);
+    }
+
+    return TRUE;
+}
+
+
+struct DSoundPlayback final : public BackendBase {
+    DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~DSoundPlayback() override;
+
+    int mixerProc();
+
+    void open(const ALCchar *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};
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(DSoundPlayback)
+};
+
+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;
+}
+
+
+FORCE_ALIGN int DSoundPlayback::mixerProc()
+{
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    DSBCAPS DSBCaps{};
+    DSBCaps.dwSize = sizeof(DSBCaps);
+    HRESULT err{mBuffer->GetCaps(&DSBCaps)};
+    if(FAILED(err))
+    {
+        ERR("Failed to get buffer caps: 0x%lx\n", err);
+        mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
+        return 1;
+    }
+
+    const size_t FrameStep{mDevice->channelsFromFmt()};
+    uint FrameSize{mDevice->frameSizeFromFmt()};
+    DWORD FragSize{mDevice->UpdateSize * FrameSize};
+
+    bool Playing{false};
+    DWORD LastCursor{0u};
+    mBuffer->GetCurrentPosition(&LastCursor, nullptr);
+    while(!mKillNow.load(std::memory_order_acquire) &&
+          mDevice->Connected.load(std::memory_order_acquire))
+    {
+        // Get current play cursor
+        DWORD PlayCursor;
+        mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
+        DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
+
+        if(avail < FragSize)
+        {
+            if(!Playing)
+            {
+                err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
+                if(FAILED(err))
+                {
+                    ERR("Failed to play buffer: 0x%lx\n", err);
+                    mDevice->handleDisconnect("Failure starting playback: 0x%lx", err);
+                    return 1;
+                }
+                Playing = true;
+            }
+
+            avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
+            if(avail != WAIT_OBJECT_0)
+                ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
+            continue;
+        }
+        avail -= avail%FragSize;
+
+        // Lock output buffer
+        void *WritePtr1, *WritePtr2;
+        DWORD WriteCnt1{0u},  WriteCnt2{0u};
+        err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
+
+        // If the buffer is lost, restore it and lock
+        if(err == DSERR_BUFFERLOST)
+        {
+            WARN("Buffer lost, restoring...\n");
+            err = mBuffer->Restore();
+            if(SUCCEEDED(err))
+            {
+                Playing = false;
+                LastCursor = 0;
+                err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
+                                    &WritePtr2, &WriteCnt2, 0);
+            }
+        }
+
+        if(SUCCEEDED(err))
+        {
+            mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
+            if(WriteCnt2 > 0)
+                mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
+
+            mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
+        }
+        else
+        {
+            ERR("Buffer lock error: %#lx\n", err);
+            mDevice->handleDisconnect("Failed to lock output buffer: 0x%lx", err);
+            return 1;
+        }
+
+        // Update old write cursor location
+        LastCursor += WriteCnt1+WriteCnt2;
+        LastCursor %= DSBCaps.dwBufferBytes;
+    }
+
+    return 0;
+}
+
+void DSoundPlayback::open(const char *name)
+{
+    HRESULT hr;
+    if(PlaybackDevices.empty())
+    {
+        /* Initialize COM to prevent name truncation */
+        HRESULT hrcom{CoInitialize(nullptr)};
+        hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
+        if(FAILED(hr))
+            ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
+        if(SUCCEEDED(hrcom))
+            CoUninitialize();
+    }
+
+    const GUID *guid{nullptr};
+    if(!name && !PlaybackDevices.empty())
+    {
+        name = PlaybackDevices[0].name.c_str();
+        guid = &PlaybackDevices[0].guid;
+    }
+    else
+    {
+        auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+            [name](const DevMap &entry) -> bool { return entry.name == name; });
+        if(iter == PlaybackDevices.cend())
+        {
+            GUID id{};
+            hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
+            if(SUCCEEDED(hr))
+                iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+                    [&id](const DevMap &entry) -> bool { return entry.guid == id; });
+            if(iter == PlaybackDevices.cend())
+                throw al::backend_exception{al::backend_error::NoDevice,
+                    "Device name \"%s\" not found", name};
+        }
+        guid = &iter->guid;
+    }
+
+    hr = DS_OK;
+    mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+    if(!mNotifyEvent) hr = E_FAIL;
+
+    //DirectSound Init code
+    if(SUCCEEDED(hr))
+        hr = DirectSoundCreate(guid, &mDS, nullptr);
+    if(SUCCEEDED(hr))
+        hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
+            hr};
+
+    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)
+    {
+    case DevFmtByte:
+        mDevice->FmtType = DevFmtUByte;
+        break;
+    case DevFmtFloat:
+        if(mDevice->Flags.test(SampleTypeRequest))
+            break;
+        /* fall-through */
+    case DevFmtUShort:
+        mDevice->FmtType = DevFmtShort;
+        break;
+    case DevFmtUInt:
+        mDevice->FmtType = DevFmtInt;
+        break;
+    case DevFmtUByte:
+    case DevFmtShort:
+    case DevFmtInt:
+        break;
+    }
+
+    WAVEFORMATEXTENSIBLE OutputType{};
+    DWORD speakers;
+    HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
+    if(SUCCEEDED(hr))
+    {
+        speakers = DSSPEAKER_CONFIG(speakers);
+        if(!mDevice->Flags.test(ChannelsRequest))
+        {
+            if(speakers == DSSPEAKER_MONO)
+                mDevice->FmtChans = DevFmtMono;
+            else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
+                mDevice->FmtChans = DevFmtStereo;
+            else if(speakers == DSSPEAKER_QUAD)
+                mDevice->FmtChans = DevFmtQuad;
+            else if(speakers == DSSPEAKER_5POINT1_SURROUND)
+                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;
+
+        switch(mDevice->FmtChans)
+        {
+        case DevFmtMono: OutputType.dwChannelMask = MONO; break;
+        case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
+            /*fall-through*/
+        case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
+        case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
+        case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break;
+        case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break;
+        case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
+        case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
+        }
+
+retry_open:
+        hr = S_OK;
+        OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
+        OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
+        OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
+        OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
+            OutputType.Format.wBitsPerSample / 8);
+        OutputType.Format.nSamplesPerSec = mDevice->Frequency;
+        OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
+            OutputType.Format.nBlockAlign;
+        OutputType.Format.cbSize = 0;
+    }
+
+    if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
+    {
+        OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+        OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
+        OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+        if(mDevice->FmtType == DevFmtFloat)
+            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+        else
+            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+        if(mPrimaryBuffer)
+            mPrimaryBuffer->Release();
+        mPrimaryBuffer = nullptr;
+    }
+    else
+    {
+        if(SUCCEEDED(hr) && !mPrimaryBuffer)
+        {
+            DSBUFFERDESC DSBDescription{};
+            DSBDescription.dwSize = sizeof(DSBDescription);
+            DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
+            hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
+        }
+        if(SUCCEEDED(hr))
+            hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
+    }
+
+    if(SUCCEEDED(hr))
+    {
+        uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+        if(num_updates > MAX_UPDATES)
+            num_updates = MAX_UPDATES;
+        mDevice->BufferSize = mDevice->UpdateSize * num_updates;
+
+        DSBUFFERDESC DSBDescription{};
+        DSBDescription.dwSize = sizeof(DSBDescription);
+        DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2
+            | DSBCAPS_GLOBALFOCUS;
+        DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
+        DSBDescription.lpwfxFormat = &OutputType.Format;
+
+        hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
+        if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
+        {
+            mDevice->FmtType = DevFmtShort;
+            goto retry_open;
+        }
+    }
+
+    if(SUCCEEDED(hr))
+    {
+        void *ptr;
+        hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
+        if(SUCCEEDED(hr))
+        {
+            mNotifies = static_cast<IDirectSoundNotify*>(ptr);
+
+            uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+            assert(num_updates <= MAX_UPDATES);
+
+            std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
+            for(uint i{0};i < num_updates;++i)
+            {
+                nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
+                nots[i].hEventNotify = mNotifyEvent;
+            }
+            if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
+                hr = E_FAIL;
+        }
+    }
+
+    if(FAILED(hr))
+    {
+        if(mNotifies)
+            mNotifies->Release();
+        mNotifies = nullptr;
+        if(mBuffer)
+            mBuffer->Release();
+        mBuffer = nullptr;
+        if(mPrimaryBuffer)
+            mPrimaryBuffer->Release();
+        mPrimaryBuffer = nullptr;
+        return false;
+    }
+
+    ResetEvent(mNotifyEvent);
+    setChannelOrderFromWFXMask(OutputType.dwChannelMask);
+
+    return true;
+}
+
+void DSoundPlayback::start()
+{
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void DSoundPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    mBuffer->Stop();
+}
+
+
+struct DSoundCapture final : public BackendBase {
+    DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~DSoundCapture() override;
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    IDirectSoundCapture *mDSC{nullptr};
+    IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
+    DWORD mBufferBytes{0u};
+    DWORD mCursor{0u};
+
+    RingBufferPtr mRing;
+
+    DEF_NEWDEL(DSoundCapture)
+};
+
+DSoundCapture::~DSoundCapture()
+{
+    if(mDSCbuffer)
+    {
+        mDSCbuffer->Stop();
+        mDSCbuffer->Release();
+        mDSCbuffer = nullptr;
+    }
+
+    if(mDSC)
+        mDSC->Release();
+    mDSC = nullptr;
+}
+
+
+void DSoundCapture::open(const char *name)
+{
+    HRESULT hr;
+    if(CaptureDevices.empty())
+    {
+        /* Initialize COM to prevent name truncation */
+        HRESULT hrcom{CoInitialize(nullptr)};
+        hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
+        if(FAILED(hr))
+            ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
+        if(SUCCEEDED(hrcom))
+            CoUninitialize();
+    }
+
+    const GUID *guid{nullptr};
+    if(!name && !CaptureDevices.empty())
+    {
+        name = CaptureDevices[0].name.c_str();
+        guid = &CaptureDevices[0].guid;
+    }
+    else
+    {
+        auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+            [name](const DevMap &entry) -> bool { return entry.name == name; });
+        if(iter == CaptureDevices.cend())
+        {
+            GUID id{};
+            hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
+            if(SUCCEEDED(hr))
+                iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+                    [&id](const DevMap &entry) -> bool { return entry.guid == id; });
+            if(iter == CaptureDevices.cend())
+                throw al::backend_exception{al::backend_error::NoDevice,
+                    "Device name \"%s\" not found", name};
+        }
+        guid = &iter->guid;
+    }
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+    case DevFmtUShort:
+    case DevFmtUInt:
+        WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
+
+    case DevFmtUByte:
+    case DevFmtShort:
+    case DevFmtInt:
+    case DevFmtFloat:
+        break;
+    }
+
+    WAVEFORMATEXTENSIBLE InputType{};
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono: InputType.dwChannelMask = MONO; break;
+    case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
+    case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
+    case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
+    case DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break;
+    case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
+    case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
+    case DevFmtAmbi3D:
+        WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
+        throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
+            DevFmtChannelsString(mDevice->FmtChans)};
+    }
+
+    InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
+    InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
+    InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
+    InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
+        InputType.Format.wBitsPerSample / 8);
+    InputType.Format.nSamplesPerSec = mDevice->Frequency;
+    InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
+        InputType.Format.nBlockAlign;
+    InputType.Format.cbSize = 0;
+    InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
+    if(mDevice->FmtType == DevFmtFloat)
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+    else
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+    if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
+    {
+        InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+        InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+    }
+
+    uint samples{mDevice->BufferSize};
+    samples = maxu(samples, 100 * mDevice->Frequency / 1000);
+
+    DSCBUFFERDESC DSCBDescription{};
+    DSCBDescription.dwSize = sizeof(DSCBDescription);
+    DSCBDescription.dwFlags = 0;
+    DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
+    DSCBDescription.lpwfxFormat = &InputType.Format;
+
+    //DirectSoundCapture Init code
+    hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
+    if(SUCCEEDED(hr))
+        mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, 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",
+            hr};
+    }
+
+    mBufferBytes = DSCBDescription.dwBufferBytes;
+    setChannelOrderFromWFXMask(InputType.dwChannelMask);
+
+    mDevice->DeviceName = name;
+}
+
+void DSoundCapture::start()
+{
+    const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failure starting capture: 0x%lx", hr};
+}
+
+void DSoundCapture::stop()
+{
+    HRESULT hr{mDSCbuffer->Stop()};
+    if(FAILED(hr))
+    {
+        ERR("stop failed: 0x%08lx\n", hr);
+        mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
+    }
+}
+
+void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+uint DSoundCapture::availableSamples()
+{
+    if(!mDevice->Connected.load(std::memory_order_acquire))
+        return static_cast<uint>(mRing->readSpace());
+
+    const uint FrameSize{mDevice->frameSizeFromFmt()};
+    const DWORD BufferBytes{mBufferBytes};
+    const DWORD LastCursor{mCursor};
+
+    DWORD ReadCursor{};
+    void *ReadPtr1{}, *ReadPtr2{};
+    DWORD ReadCnt1{},  ReadCnt2{};
+    HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
+    if(SUCCEEDED(hr))
+    {
+        const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
+        if(!NumBytes) return static_cast<uint>(mRing->readSpace());
+        hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
+    }
+    if(SUCCEEDED(hr))
+    {
+        mRing->write(ReadPtr1, ReadCnt1/FrameSize);
+        if(ReadPtr2 != nullptr && ReadCnt2 > 0)
+            mRing->write(ReadPtr2, ReadCnt2/FrameSize);
+        hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
+        mCursor = ReadCursor;
+    }
+
+    if(FAILED(hr))
+    {
+        ERR("update failed: 0x%08lx\n", hr);
+        mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
+    }
+
+    return static_cast<uint>(mRing->readSpace());
+}
+
+} // namespace
+
+
+BackendFactory &DSoundBackendFactory::getFactory()
+{
+    static DSoundBackendFactory factory{};
+    return factory;
+}
+
+bool DSoundBackendFactory::init()
+{
+#ifdef HAVE_DYNLOAD
+    if(!ds_handle)
+    {
+        ds_handle = LoadLib("dsound.dll");
+        if(!ds_handle)
+        {
+            ERR("Failed to load dsound.dll\n");
+            return false;
+        }
+
+#define LOAD_FUNC(f) do {                                                     \
+    p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f));        \
+    if(!p##f)                                                                 \
+    {                                                                         \
+        CloseLib(ds_handle);                                                  \
+        ds_handle = nullptr;                                                  \
+        return false;                                                         \
+    }                                                                         \
+} while(0)
+        LOAD_FUNC(DirectSoundCreate);
+        LOAD_FUNC(DirectSoundEnumerateW);
+        LOAD_FUNC(DirectSoundCaptureCreate);
+        LOAD_FUNC(DirectSoundCaptureEnumerateW);
+#undef LOAD_FUNC
+    }
+#endif
+    return true;
+}
+
+bool DSoundBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string DSoundBackendFactory::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);
+    };
+
+    /* Initialize COM to prevent name truncation */
+    HRESULT hr;
+    HRESULT hrcom{CoInitialize(nullptr)};
+    switch(type)
+    {
+    case BackendType::Playback:
+        PlaybackDevices.clear();
+        hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
+        if(FAILED(hr))
+            ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
+        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        break;
+
+    case BackendType::Capture:
+        CaptureDevices.clear();
+        hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
+        if(FAILED(hr))
+            ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+    if(SUCCEEDED(hrcom))
+        CoUninitialize();
+
+    return outnames;
+}
+
+BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new DSoundPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new DSoundCapture{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/dsound.h

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

+ 0 - 607
Engine/lib/openal-soft/Alc/backends/jack.c

@@ -1,607 +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 <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <jack/jack.h>
-#include <jack/ringbuffer.h>
-
-
-static const ALCchar jackDevice[] = "JACK Default";
-
-
-#ifdef HAVE_DYNLOAD
-#define JACK_FUNCS(MAGIC)          \
-    MAGIC(jack_client_open);       \
-    MAGIC(jack_client_close);      \
-    MAGIC(jack_client_name_size);  \
-    MAGIC(jack_get_client_name);   \
-    MAGIC(jack_connect);           \
-    MAGIC(jack_activate);          \
-    MAGIC(jack_deactivate);        \
-    MAGIC(jack_port_register);     \
-    MAGIC(jack_port_unregister);   \
-    MAGIC(jack_port_get_buffer);   \
-    MAGIC(jack_port_name);         \
-    MAGIC(jack_get_ports);         \
-    MAGIC(jack_free);              \
-    MAGIC(jack_get_sample_rate);   \
-    MAGIC(jack_set_error_function); \
-    MAGIC(jack_set_process_callback); \
-    MAGIC(jack_set_buffer_size_callback); \
-    MAGIC(jack_set_buffer_size);   \
-    MAGIC(jack_get_buffer_size);
-
-static void *jack_handle;
-#define MAKE_FUNC(f) static __typeof(f) * p##f
-JACK_FUNCS(MAKE_FUNC);
-static __typeof(jack_error_callback) * pjack_error_callback;
-#undef MAKE_FUNC
-
-#define jack_client_open pjack_client_open
-#define jack_client_close pjack_client_close
-#define jack_client_name_size pjack_client_name_size
-#define jack_get_client_name pjack_get_client_name
-#define jack_connect pjack_connect
-#define jack_activate pjack_activate
-#define jack_deactivate pjack_deactivate
-#define jack_port_register pjack_port_register
-#define jack_port_unregister pjack_port_unregister
-#define jack_port_get_buffer pjack_port_get_buffer
-#define jack_port_name pjack_port_name
-#define jack_get_ports pjack_get_ports
-#define jack_free pjack_free
-#define jack_get_sample_rate pjack_get_sample_rate
-#define jack_set_error_function pjack_set_error_function
-#define jack_set_process_callback pjack_set_process_callback
-#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
-#define jack_set_buffer_size pjack_set_buffer_size
-#define jack_get_buffer_size pjack_get_buffer_size
-#define jack_error_callback (*pjack_error_callback)
-#endif
-
-
-static jack_options_t ClientOptions = JackNullOption;
-
-static ALCboolean jack_load(void)
-{
-    ALCboolean error = ALC_FALSE;
-
-#ifdef HAVE_DYNLOAD
-    if(!jack_handle)
-    {
-        al_string missing_funcs = AL_STRING_INIT_STATIC();
-
-#ifdef _WIN32
-#define JACKLIB "libjack.dll"
-#else
-#define JACKLIB "libjack.so.0"
-#endif
-        jack_handle = LoadLib(JACKLIB);
-        if(!jack_handle)
-        {
-            WARN("Failed to load %s\n", JACKLIB);
-            return ALC_FALSE;
-        }
-
-        error = ALC_FALSE;
-#define LOAD_FUNC(f) do {                                                     \
-    p##f = GetSymbol(jack_handle, #f);                                        \
-    if(p##f == NULL) {                                                        \
-        error = ALC_TRUE;                                                     \
-        alstr_append_cstr(&missing_funcs, "\n" #f);                           \
-    }                                                                         \
-} while(0)
-        JACK_FUNCS(LOAD_FUNC);
-#undef LOAD_FUNC
-        /* Optional symbols. These don't exist in all versions of JACK. */
-#define LOAD_SYM(f) p##f = GetSymbol(jack_handle, #f)
-        LOAD_SYM(jack_error_callback);
-#undef LOAD_SYM
-
-        if(error)
-        {
-            WARN("Missing expected functions:%s\n", alstr_get_cstr(missing_funcs));
-            CloseLib(jack_handle);
-            jack_handle = NULL;
-        }
-        alstr_reset(&missing_funcs);
-    }
-#endif
-
-    return !error;
-}
-
-
-typedef struct ALCjackPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    jack_client_t *Client;
-    jack_port_t *Port[MAX_OUTPUT_CHANNELS];
-
-    ll_ringbuffer_t *Ring;
-    alsem_t Sem;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCjackPlayback;
-
-static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg);
-
-static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg);
-static int ALCjackPlayback_mixerProc(void *arg);
-
-static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device);
-static void ALCjackPlayback_Destruct(ALCjackPlayback *self);
-static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name);
-static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *self);
-static ALCboolean ALCjackPlayback_start(ALCjackPlayback *self);
-static void ALCjackPlayback_stop(ALCjackPlayback *self);
-static DECLARE_FORWARD2(ALCjackPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, ALCuint, availableSamples)
-static ClockLatency ALCjackPlayback_getClockLatency(ALCjackPlayback *self);
-static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCjackPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCjackPlayback);
-
-
-static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device)
-{
-    ALuint i;
-
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCjackPlayback, ALCbackend, self);
-
-    alsem_init(&self->Sem, 0);
-
-    self->Client = NULL;
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        self->Port[i] = NULL;
-    self->Ring = NULL;
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCjackPlayback_Destruct(ALCjackPlayback *self)
-{
-    ALuint i;
-
-    if(self->Client)
-    {
-        for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        {
-            if(self->Port[i])
-                jack_port_unregister(self->Client, self->Port[i]);
-            self->Port[i] = NULL;
-        }
-        jack_client_close(self->Client);
-        self->Client = NULL;
-    }
-
-    alsem_destroy(&self->Sem);
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg)
-{
-    ALCjackPlayback *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    ALuint bufsize;
-
-    ALCjackPlayback_lock(self);
-    device->UpdateSize = numframes;
-    device->NumUpdates = 2;
-
-    bufsize = device->UpdateSize;
-    if(ConfigValueUInt(alstr_get_cstr(device->DeviceName), "jack", "buffer-size", &bufsize))
-        bufsize = maxu(NextPowerOf2(bufsize), device->UpdateSize);
-    device->NumUpdates = (bufsize+device->UpdateSize) / device->UpdateSize;
-
-    TRACE("%u update size x%u\n", device->UpdateSize, device->NumUpdates);
-
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = ll_ringbuffer_create(bufsize,
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
-        true
-    );
-    if(!self->Ring)
-    {
-        ERR("Failed to reallocate ringbuffer\n");
-        aluHandleDisconnect(device, "Failed to reallocate %u-sample buffer", bufsize);
-    }
-    ALCjackPlayback_unlock(self);
-    return 0;
-}
-
-
-static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg)
-{
-    ALCjackPlayback *self = arg;
-    jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
-    ll_ringbuffer_data_t data[2];
-    jack_nframes_t total = 0;
-    jack_nframes_t todo;
-    ALsizei i, c, numchans;
-
-    ll_ringbuffer_get_read_vector(self->Ring, data);
-
-    for(c = 0;c < MAX_OUTPUT_CHANNELS && self->Port[c];c++)
-        out[c] = jack_port_get_buffer(self->Port[c], numframes);
-    numchans = c;
-
-    todo = minu(numframes, data[0].len);
-    for(c = 0;c < numchans;c++)
-    {
-        const ALfloat *restrict in = ((ALfloat*)data[0].buf) + c;
-        for(i = 0;(jack_nframes_t)i < todo;i++)
-            out[c][i] = in[i*numchans];
-        out[c] += todo;
-    }
-    total += todo;
-
-    todo = minu(numframes-total, data[1].len);
-    if(todo > 0)
-    {
-        for(c = 0;c < numchans;c++)
-        {
-            const ALfloat *restrict in = ((ALfloat*)data[1].buf) + c;
-            for(i = 0;(jack_nframes_t)i < todo;i++)
-                out[c][i] = in[i*numchans];
-            out[c] += todo;
-        }
-        total += todo;
-    }
-
-    ll_ringbuffer_read_advance(self->Ring, total);
-    alsem_post(&self->Sem);
-
-    if(numframes > total)
-    {
-        todo = numframes-total;
-        for(c = 0;c < numchans;c++)
-        {
-            for(i = 0;(jack_nframes_t)i < todo;i++)
-                out[c][i] = 0.0f;
-        }
-    }
-
-    return 0;
-}
-
-static int ALCjackPlayback_mixerProc(void *arg)
-{
-    ALCjackPlayback *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    ll_ringbuffer_data_t data[2];
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    ALCjackPlayback_lock(self);
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        ALuint todo, len1, len2;
-
-        if(ll_ringbuffer_write_space(self->Ring) < device->UpdateSize)
-        {
-            ALCjackPlayback_unlock(self);
-            alsem_wait(&self->Sem);
-            ALCjackPlayback_lock(self);
-            continue;
-        }
-
-        ll_ringbuffer_get_write_vector(self->Ring, data);
-        todo  = data[0].len + data[1].len;
-        todo -= todo%device->UpdateSize;
-
-        len1 = minu(data[0].len, todo);
-        len2 = minu(data[1].len, todo-len1);
-
-        aluMixData(device, data[0].buf, len1);
-        if(len2 > 0)
-            aluMixData(device, data[1].buf, len2);
-        ll_ringbuffer_write_advance(self->Ring, todo);
-    }
-    ALCjackPlayback_unlock(self);
-
-    return 0;
-}
-
-
-static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const char *client_name = "alsoft";
-    jack_status_t status;
-
-    if(!name)
-        name = jackDevice;
-    else if(strcmp(name, jackDevice) != 0)
-        return ALC_INVALID_VALUE;
-
-    self->Client = jack_client_open(client_name, ClientOptions, &status, NULL);
-    if(self->Client == NULL)
-    {
-        ERR("jack_client_open() failed, status = 0x%02x\n", status);
-        return ALC_INVALID_VALUE;
-    }
-    if((status&JackServerStarted))
-        TRACE("JACK server started\n");
-    if((status&JackNameNotUnique))
-    {
-        client_name = jack_get_client_name(self->Client);
-        TRACE("Client name not unique, got `%s' instead\n", client_name);
-    }
-
-    jack_set_process_callback(self->Client, ALCjackPlayback_process, self);
-    jack_set_buffer_size_callback(self->Client, ALCjackPlayback_bufferSizeNotify, self);
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALsizei numchans, i;
-    ALuint bufsize;
-
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-    {
-        if(self->Port[i])
-            jack_port_unregister(self->Client, self->Port[i]);
-        self->Port[i] = NULL;
-    }
-
-    /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
-     * ready for when requested.
-     */
-    device->Frequency = jack_get_sample_rate(self->Client);
-    device->UpdateSize = jack_get_buffer_size(self->Client);
-    device->NumUpdates = 2;
-
-    bufsize = device->UpdateSize;
-    if(ConfigValueUInt(alstr_get_cstr(device->DeviceName), "jack", "buffer-size", &bufsize))
-        bufsize = maxu(NextPowerOf2(bufsize), device->UpdateSize);
-    device->NumUpdates = (bufsize+device->UpdateSize) / device->UpdateSize;
-
-    /* Force 32-bit float output. */
-    device->FmtType = DevFmtFloat;
-
-    numchans = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    for(i = 0;i < numchans;i++)
-    {
-        char name[64];
-        snprintf(name, sizeof(name), "channel_%d", i+1);
-        self->Port[i] = jack_port_register(self->Client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
-        if(self->Port[i] == NULL)
-        {
-            ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(device->FmtChans));
-            if(i == 0) return ALC_FALSE;
-            break;
-        }
-    }
-    if(i < numchans)
-    {
-        if(i == 1)
-            device->FmtChans = DevFmtMono;
-        else
-        {
-            for(--i;i >= 2;i--)
-            {
-                jack_port_unregister(self->Client, self->Port[i]);
-                self->Port[i] = NULL;
-            }
-            device->FmtChans = DevFmtStereo;
-        }
-    }
-
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = ll_ringbuffer_create(bufsize,
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
-        true
-    );
-    if(!self->Ring)
-    {
-        ERR("Failed to allocate ringbuffer\n");
-        return ALC_FALSE;
-    }
-
-    SetDefaultChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCjackPlayback_start(ALCjackPlayback *self)
-{
-    const char **ports;
-    ALsizei i;
-
-    if(jack_activate(self->Client))
-    {
-        ERR("Failed to activate client\n");
-        return ALC_FALSE;
-    }
-
-    ports = jack_get_ports(self->Client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
-    if(ports == NULL)
-    {
-        ERR("No physical playback ports found\n");
-        jack_deactivate(self->Client);
-        return ALC_FALSE;
-    }
-    for(i = 0;i < MAX_OUTPUT_CHANNELS && self->Port[i];i++)
-    {
-        if(!ports[i])
-        {
-            ERR("No physical playback port for \"%s\"\n", jack_port_name(self->Port[i]));
-            break;
-        }
-        if(jack_connect(self->Client, jack_port_name(self->Port[i]), ports[i]))
-            ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(self->Port[i]), ports[i]);
-    }
-    jack_free(ports);
-
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCjackPlayback_mixerProc, self) != althrd_success)
-    {
-        jack_deactivate(self->Client);
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCjackPlayback_stop(ALCjackPlayback *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-
-    alsem_post(&self->Sem);
-    althrd_join(self->thread, &res);
-
-    jack_deactivate(self->Client);
-}
-
-
-static ClockLatency ALCjackPlayback_getClockLatency(ALCjackPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ClockLatency ret;
-
-    ALCjackPlayback_lock(self);
-    ret.ClockTime = GetDeviceClockTime(device);
-    ret.Latency = ll_ringbuffer_read_space(self->Ring) * DEVICE_CLOCK_RES /
-                  device->Frequency;
-    ALCjackPlayback_unlock(self);
-
-    return ret;
-}
-
-
-static void jack_msg_handler(const char *message)
-{
-    WARN("%s\n", message);
-}
-
-typedef struct ALCjackBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCjackBackendFactory;
-#define ALCJACKBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCjackBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCjackBackendFactory_init(ALCjackBackendFactory* UNUSED(self))
-{
-    void (*old_error_cb)(const char*);
-    jack_client_t *client;
-    jack_status_t status;
-
-    if(!jack_load())
-        return ALC_FALSE;
-
-    if(!GetConfigValueBool(NULL, "jack", "spawn-server", 0))
-        ClientOptions |= JackNoStartServer;
-
-    old_error_cb = (&jack_error_callback ? jack_error_callback : NULL);
-    jack_set_error_function(jack_msg_handler);
-    client = jack_client_open("alsoft", ClientOptions, &status, NULL);
-    jack_set_error_function(old_error_cb);
-    if(client == NULL)
-    {
-        WARN("jack_client_open() failed, 0x%02x\n", status);
-        if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
-            ERR("Unable to connect to JACK server\n");
-        return ALC_FALSE;
-    }
-
-    jack_client_close(client);
-    return ALC_TRUE;
-}
-
-static void ALCjackBackendFactory_deinit(ALCjackBackendFactory* UNUSED(self))
-{
-#ifdef HAVE_DYNLOAD
-    if(jack_handle)
-        CloseLib(jack_handle);
-    jack_handle = NULL;
-#endif
-}
-
-static ALCboolean ALCjackBackendFactory_querySupport(ALCjackBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCjackBackendFactory_probe(ALCjackBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(jackDevice);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            break;
-    }
-}
-
-static ALCbackend* ALCjackBackendFactory_createBackend(ALCjackBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCjackPlayback *backend;
-        NEW_OBJ(backend, ALCjackPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCjackBackendFactory);
-
-
-ALCbackendFactory *ALCjackBackendFactory_getFactory(void)
-{
-    static ALCjackBackendFactory factory = ALCJACKBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 601 - 0
Engine/lib/openal-soft/Alc/backends/jack.cpp

@@ -0,0 +1,601 @@
+/**
+ * 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 "backends/jack.h"
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <memory.h>
+
+#include <array>
+#include <thread>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+#include <jack/jack.h>
+#include <jack/ringbuffer.h>
+
+
+namespace {
+
+constexpr char jackDevice[] = "JACK Default";
+
+
+#ifdef HAVE_DYNLOAD
+#define JACK_FUNCS(MAGIC)          \
+    MAGIC(jack_client_open);       \
+    MAGIC(jack_client_close);      \
+    MAGIC(jack_client_name_size);  \
+    MAGIC(jack_get_client_name);   \
+    MAGIC(jack_connect);           \
+    MAGIC(jack_activate);          \
+    MAGIC(jack_deactivate);        \
+    MAGIC(jack_port_register);     \
+    MAGIC(jack_port_unregister);   \
+    MAGIC(jack_port_get_buffer);   \
+    MAGIC(jack_port_name);         \
+    MAGIC(jack_get_ports);         \
+    MAGIC(jack_free);              \
+    MAGIC(jack_get_sample_rate);   \
+    MAGIC(jack_set_error_function); \
+    MAGIC(jack_set_process_callback); \
+    MAGIC(jack_set_buffer_size_callback); \
+    MAGIC(jack_set_buffer_size);   \
+    MAGIC(jack_get_buffer_size);
+
+void *jack_handle;
+#define MAKE_FUNC(f) decltype(f) * p##f
+JACK_FUNCS(MAKE_FUNC)
+decltype(jack_error_callback) * pjack_error_callback;
+#undef MAKE_FUNC
+
+#ifndef IN_IDE_PARSER
+#define jack_client_open pjack_client_open
+#define jack_client_close pjack_client_close
+#define jack_client_name_size pjack_client_name_size
+#define jack_get_client_name pjack_get_client_name
+#define jack_connect pjack_connect
+#define jack_activate pjack_activate
+#define jack_deactivate pjack_deactivate
+#define jack_port_register pjack_port_register
+#define jack_port_unregister pjack_port_unregister
+#define jack_port_get_buffer pjack_port_get_buffer
+#define jack_port_name pjack_port_name
+#define jack_get_ports pjack_get_ports
+#define jack_free pjack_free
+#define jack_get_sample_rate pjack_get_sample_rate
+#define jack_set_error_function pjack_set_error_function
+#define jack_set_process_callback pjack_set_process_callback
+#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
+#define jack_set_buffer_size pjack_set_buffer_size
+#define jack_get_buffer_size pjack_get_buffer_size
+#define jack_error_callback (*pjack_error_callback)
+#endif
+#endif
+
+
+jack_options_t ClientOptions = JackNullOption;
+
+bool jack_load()
+{
+    bool error{false};
+
+#ifdef HAVE_DYNLOAD
+    if(!jack_handle)
+    {
+        std::string missing_funcs;
+
+#ifdef _WIN32
+#define JACKLIB "libjack.dll"
+#else
+#define JACKLIB "libjack.so.0"
+#endif
+        jack_handle = LoadLib(JACKLIB);
+        if(!jack_handle)
+        {
+            WARN("Failed to load %s\n", JACKLIB);
+            return false;
+        }
+
+        error = false;
+#define LOAD_FUNC(f) do {                                                     \
+    p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f));      \
+    if(p##f == nullptr) {                                                     \
+        error = true;                                                         \
+        missing_funcs += "\n" #f;                                             \
+    }                                                                         \
+} while(0)
+        JACK_FUNCS(LOAD_FUNC);
+#undef LOAD_FUNC
+        /* Optional symbols. These don't exist in all versions of JACK. */
+#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
+        LOAD_SYM(jack_error_callback);
+#undef LOAD_SYM
+
+        if(error)
+        {
+            WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+            CloseLib(jack_handle);
+            jack_handle = nullptr;
+        }
+    }
+#endif
+
+    return !error;
+}
+
+
+struct DeviceEntry {
+    std::string mName;
+    std::string mPattern;
+};
+
+al::vector<DeviceEntry> PlaybackList;
+
+
+void EnumerateDevices(al::vector<DeviceEntry> &list)
+{
+    al::vector<DeviceEntry>{}.swap(list);
+
+    list.emplace_back(DeviceEntry{jackDevice, ""});
+
+    std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")};
+    size_t strpos{0};
+    while(strpos < customList.size())
+    {
+        size_t nextpos{customList.find(';', strpos)};
+        size_t seppos{customList.find('=', strpos)};
+        if(seppos >= nextpos || seppos == strpos)
+        {
+            const std::string entry{customList.substr(strpos, nextpos-strpos)};
+            ERR("Invalid device entry: \"%s\"\n", entry.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())
+        {
+            name = customList.substr(strpos, seppos-strpos);
+            name += " #";
+            name += std::to_string(++count);
+        }
+
+        ++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() override;
+
+    int process(jack_nframes_t numframes) noexcept;
+    static int processC(jack_nframes_t numframes, void *arg) noexcept
+    { return static_cast<JackPlayback*>(arg)->process(numframes); }
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+    ClockLatency getClockLatency() override;
+
+    std::string mPortPattern;
+
+    jack_client_t *mClient{nullptr};
+    std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
+
+    std::mutex mMutex;
+
+    std::atomic<bool> mPlaying{false};
+    RingBufferPtr mRing;
+    al::semaphore mSem;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(JackPlayback)
+};
+
+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); }
+    );
+    mPort.fill(nullptr);
+    jack_client_close(mClient);
+    mClient = nullptr;
+}
+
+
+int JackPlayback::process(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) break;
+        out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+    }
+
+    jack_nframes_t total{0};
+    if LIKELY(mPlaying.load(std::memory_order_acquire))
+    {
+        auto data = mRing->getReadVector();
+        jack_nframes_t todo{minu(numframes, static_cast<uint>(data.first.len))};
+        auto write_first = [&data,numchans,todo](float *outbuf) -> float*
+        {
+            const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
+            auto deinterlace_input = [&in,numchans]() noexcept -> float
+            {
+                float ret{*in};
+                in += numchans;
+                return ret;
+            };
+            std::generate_n(outbuf, todo, deinterlace_input);
+            data.first.buf += sizeof(float);
+            return outbuf + todo;
+        };
+        std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first);
+        total += todo;
+
+        todo = minu(numframes-total, static_cast<uint>(data.second.len));
+        if(todo > 0)
+        {
+            auto write_second = [&data,numchans,todo](float *outbuf) -> float*
+            {
+                const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
+                auto deinterlace_input = [&in,numchans]() noexcept -> float
+                {
+                    float ret{*in};
+                    in += numchans;
+                    return ret;
+                };
+                std::generate_n(outbuf, todo, deinterlace_input);
+                data.second.buf += sizeof(float);
+                return outbuf + todo;
+            };
+            std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second);
+            total += todo;
+        }
+
+        mRing->readAdvance(total);
+        mSem.post();
+    }
+
+    if(numframes > total)
+    {
+        const jack_nframes_t todo{numframes - total};
+        auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
+        std::for_each(out.begin(), out.begin()+numchans, clear_buf);
+    }
+
+    return 0;
+}
+
+int JackPlayback::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))
+    {
+        if(mRing->writeSpace() < mDevice->UpdateSize)
+        {
+            mSem.wait();
+            continue;
+        }
+
+        auto data = mRing->getWriteVector();
+        size_t todo{data.first.len + data.second.len};
+        todo -= todo%mDevice->UpdateSize;
+
+        const auto len1 = static_cast<uint>(minz(data.first.len, todo));
+        const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
+
+        std::lock_guard<std::mutex> _{mMutex};
+        mDevice->renderSamples(data.first.buf, len1, frame_step);
+        if(len2 > 0)
+            mDevice->renderSamples(data.second.buf, len2, frame_step);
+        mRing->writeAdvance(todo);
+    }
+
+    return 0;
+}
+
+
+void JackPlayback::open(const char *name)
+{
+    mPortPattern.clear();
+
+    if(!name)
+        name = jackDevice;
+    else if(strcmp(name, jackDevice) != 0)
+    {
+        if(PlaybackList.empty())
+            EnumerateDevices(PlaybackList);
+
+        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};
+        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);
+
+    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); });
+    mPort.fill(nullptr);
+
+    /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
+     * ready for when requested.
+     */
+    mDevice->Frequency = jack_get_sample_rate(mClient);
+    mDevice->UpdateSize = jack_get_buffer_size(mClient);
+    mDevice->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;
+
+    /* Force 32-bit float output. */
+    mDevice->FmtType = DevFmtFloat;
+
+    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;
+        });
+    if(bad_port != ports_end)
+    {
+        ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
+        if(bad_port == mPort.begin()) return false;
+
+        if(bad_port == mPort.begin()+1)
+            mDevice->FmtChans = DevFmtMono;
+        else
+        {
+            ports_end = mPort.begin()+2;
+            while(bad_port != ports_end)
+            {
+                jack_port_unregister(mClient, *(--bad_port));
+                *bad_port = nullptr;
+            }
+            mDevice->FmtChans = DevFmtStereo;
+        }
+    }
+
+    setDefaultChannelOrder();
+
+    return true;
+}
+
+void JackPlayback::start()
+{
+    if(jack_activate(mClient))
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
+
+    const char *devname{mDevice->DeviceName.c_str()};
+    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)
+        {
+            jack_deactivate(mClient);
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "No physical playback ports found"};
+        }
+        auto connect_port = [this](const jack_port_t *port, const char *pname) -> bool
+        {
+            if(!port) return false;
+            if(!pname)
+            {
+                ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
+                return false;
+            }
+            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);
+    }
+
+    /* Reconfigure buffer metrics in case the server changed it since the reset
+     * (it won't change again after jack_activate), then allocate the ring
+     * buffer with the appropriate size.
+     */
+    mDevice->Frequency = jack_get_sample_rate(mClient);
+    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 {
+        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();
+
+    jack_deactivate(mClient);
+    mPlaying.store(false, std::memory_order_release);
+}
+
+
+ClockLatency JackPlayback::getClockLatency()
+{
+    ClockLatency ret;
+
+    std::lock_guard<std::mutex> _{mMutex};
+    ret.ClockTime = GetDeviceClockTime(mDevice);
+    ret.Latency  = std::chrono::seconds{mRing->readSpace()};
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+
+void jack_msg_handler(const char *message)
+{
+    WARN("%s\n", message);
+}
+
+} // namespace
+
+bool JackBackendFactory::init()
+{
+    if(!jack_load())
+        return false;
+
+    if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
+        ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
+
+    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_set_error_function(old_error_cb);
+    if(!client)
+    {
+        WARN("jack_client_open() failed, 0x%02x\n", status);
+        if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
+            ERR("Unable to connect to JACK server\n");
+        return false;
+    }
+
+    jack_client_close(client);
+    return true;
+}
+
+bool JackBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback); }
+
+std::string JackBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    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);
+        std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
+        break;
+    case BackendType::Capture:
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new JackPlayback{device}};
+    return nullptr;
+}
+
+BackendFactory &JackBackendFactory::getFactory()
+{
+    static JackBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/jack.h

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

+ 0 - 128
Engine/lib/openal-soft/Alc/backends/loopback.c

@@ -1,128 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2011 by Chris Robinson
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alu.h"
-
-#include "backends/base.h"
-
-
-typedef struct ALCloopback {
-    DERIVE_FROM_TYPE(ALCbackend);
-} ALCloopback;
-
-static void ALCloopback_Construct(ALCloopback *self, ALCdevice *device);
-static DECLARE_FORWARD(ALCloopback, ALCbackend, void, Destruct)
-static ALCenum ALCloopback_open(ALCloopback *self, const ALCchar *name);
-static ALCboolean ALCloopback_reset(ALCloopback *self);
-static ALCboolean ALCloopback_start(ALCloopback *self);
-static void ALCloopback_stop(ALCloopback *self);
-static DECLARE_FORWARD2(ALCloopback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCloopback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCloopback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCloopback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCloopback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCloopback)
-DEFINE_ALCBACKEND_VTABLE(ALCloopback);
-
-
-static void ALCloopback_Construct(ALCloopback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCloopback, ALCbackend, self);
-}
-
-
-static ALCenum ALCloopback_open(ALCloopback *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    alstr_copy_cstr(&device->DeviceName, name);
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCloopback_reset(ALCloopback *self)
-{
-    SetDefaultWFXChannelOrder(STATIC_CAST(ALCbackend, self)->mDevice);
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCloopback_start(ALCloopback* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static void ALCloopback_stop(ALCloopback* UNUSED(self))
-{
-}
-
-
-typedef struct ALCloopbackFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCloopbackFactory;
-#define ALCNULLBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCloopbackFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCloopbackFactory_getFactory(void);
-static ALCboolean ALCloopbackFactory_init(ALCloopbackFactory *self);
-static DECLARE_FORWARD(ALCloopbackFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCloopbackFactory_querySupport(ALCloopbackFactory *self, ALCbackend_Type type);
-static void ALCloopbackFactory_probe(ALCloopbackFactory *self, enum DevProbe type);
-static ALCbackend* ALCloopbackFactory_createBackend(ALCloopbackFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCloopbackFactory);
-
-
-ALCbackendFactory *ALCloopbackFactory_getFactory(void)
-{
-    static ALCloopbackFactory factory = ALCNULLBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-static ALCboolean ALCloopbackFactory_init(ALCloopbackFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCloopbackFactory_querySupport(ALCloopbackFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Loopback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCloopbackFactory_probe(ALCloopbackFactory* UNUSED(self), enum DevProbe UNUSED(type))
-{
-}
-
-static ALCbackend* ALCloopbackFactory_createBackend(ALCloopbackFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Loopback)
-    {
-        ALCloopback *backend;
-        NEW_OBJ(backend, ALCloopback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 79 - 0
Engine/lib/openal-soft/Alc/backends/loopback.cpp

@@ -0,0 +1,79 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2011 by Chris Robinson
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "backends/loopback.h"
+
+#include "alcmain.h"
+#include "alu.h"
+
+
+namespace {
+
+struct LoopbackBackend final : public BackendBase {
+    LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+
+    void open(const ALCchar *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    DEF_NEWDEL(LoopbackBackend)
+};
+
+
+void LoopbackBackend::open(const ALCchar *name)
+{
+    mDevice->DeviceName = name;
+}
+
+bool LoopbackBackend::reset()
+{
+    setDefaultWFXChannelOrder();
+    return true;
+}
+
+void LoopbackBackend::start()
+{ }
+
+void LoopbackBackend::stop()
+{ }
+
+} // namespace
+
+
+bool LoopbackBackendFactory::init()
+{ return true; }
+
+bool LoopbackBackendFactory::querySupport(BackendType)
+{ return true; }
+
+std::string LoopbackBackendFactory::probe(BackendType)
+{ return std::string{}; }
+
+BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
+{ return BackendPtr{new LoopbackBackend{device}}; }
+
+BackendFactory &LoopbackBackendFactory::getFactory()
+{
+    static LoopbackBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/loopback.h

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

+ 0 - 221
Engine/lib/openal-soft/Alc/backends/null.c

@@ -1,221 +0,0 @@
-/**
- * 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 <stdlib.h>
-#ifdef HAVE_WINDOWS_H
-#include <windows.h>
-#endif
-
-#include "alMain.h"
-#include "alu.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-
-typedef struct ALCnullBackend {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    ATOMIC(int) killNow;
-    althrd_t thread;
-} ALCnullBackend;
-
-static int ALCnullBackend_mixerProc(void *ptr);
-
-static void ALCnullBackend_Construct(ALCnullBackend *self, ALCdevice *device);
-static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, Destruct)
-static ALCenum ALCnullBackend_open(ALCnullBackend *self, const ALCchar *name);
-static ALCboolean ALCnullBackend_reset(ALCnullBackend *self);
-static ALCboolean ALCnullBackend_start(ALCnullBackend *self);
-static void ALCnullBackend_stop(ALCnullBackend *self);
-static DECLARE_FORWARD2(ALCnullBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCnullBackend, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCnullBackend, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCnullBackend, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCnullBackend)
-
-DEFINE_ALCBACKEND_VTABLE(ALCnullBackend);
-
-
-static const ALCchar nullDevice[] = "No Output";
-
-
-static void ALCnullBackend_Construct(ALCnullBackend *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCnullBackend, ALCbackend, self);
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-
-static int ALCnullBackend_mixerProc(void *ptr)
-{
-    ALCnullBackend *self = (ALCnullBackend*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct timespec now, start;
-    ALuint64 avail, done;
-    const long restTime = (long)((ALuint64)device->UpdateSize * 1000000000 /
-                                 device->Frequency / 2);
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    done = 0;
-    if(altimespec_get(&start, AL_TIME_UTC) != AL_TIME_UTC)
-    {
-        ERR("Failed to get starting time\n");
-        return 1;
-    }
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        if(altimespec_get(&now, AL_TIME_UTC) != AL_TIME_UTC)
-        {
-            ERR("Failed to get current time\n");
-            return 1;
-        }
-
-        avail  = (now.tv_sec - start.tv_sec) * device->Frequency;
-        avail += (ALint64)(now.tv_nsec - start.tv_nsec) * device->Frequency / 1000000000;
-        if(avail < done)
-        {
-            /* Oops, time skipped backwards. Reset the number of samples done
-             * with one update available since we (likely) just came back from
-             * sleeping. */
-            done = avail - device->UpdateSize;
-        }
-
-        if(avail-done < device->UpdateSize)
-            al_nssleep(restTime);
-        else while(avail-done >= device->UpdateSize)
-        {
-            ALCnullBackend_lock(self);
-            aluMixData(device, NULL, device->UpdateSize);
-            ALCnullBackend_unlock(self);
-            done += device->UpdateSize;
-        }
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCnullBackend_open(ALCnullBackend *self, const ALCchar *name)
-{
-    ALCdevice *device;
-
-    if(!name)
-        name = nullDevice;
-    else if(strcmp(name, nullDevice) != 0)
-        return ALC_INVALID_VALUE;
-
-    device = STATIC_CAST(ALCbackend, self)->mDevice;
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCnullBackend_reset(ALCnullBackend *self)
-{
-    SetDefaultWFXChannelOrder(STATIC_CAST(ALCbackend, self)->mDevice);
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCnullBackend_start(ALCnullBackend *self)
-{
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCnullBackend_mixerProc, self) != althrd_success)
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCnullBackend_stop(ALCnullBackend *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &res);
-}
-
-
-typedef struct ALCnullBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCnullBackendFactory;
-#define ALCNULLBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCnullBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCnullBackendFactory_getFactory(void);
-
-static ALCboolean ALCnullBackendFactory_init(ALCnullBackendFactory *self);
-static DECLARE_FORWARD(ALCnullBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCnullBackendFactory_querySupport(ALCnullBackendFactory *self, ALCbackend_Type type);
-static void ALCnullBackendFactory_probe(ALCnullBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCnullBackendFactory_createBackend(ALCnullBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCnullBackendFactory);
-
-
-ALCbackendFactory *ALCnullBackendFactory_getFactory(void)
-{
-    static ALCnullBackendFactory factory = ALCNULLBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCnullBackendFactory_init(ALCnullBackendFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCnullBackendFactory_querySupport(ALCnullBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCnullBackendFactory_probe(ALCnullBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(nullDevice);
-            break;
-        case CAPTURE_DEVICE_PROBE:
-            break;
-    }
-}
-
-static ALCbackend* ALCnullBackendFactory_createBackend(ALCnullBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCnullBackend *backend;
-        NEW_OBJ(backend, ALCnullBackend)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 179 - 0
Engine/lib/openal-soft/Alc/backends/null.cpp

@@ -0,0 +1,179 @@
+/**
+ * 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 "backends/null.h"
+
+#include <exception>
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <cstring>
+#include <functional>
+#include <thread>
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alu.h"
+#include "threads.h"
+
+
+namespace {
+
+using std::chrono::seconds;
+using std::chrono::milliseconds;
+using std::chrono::nanoseconds;
+
+constexpr char nullDevice[] = "No Output";
+
+
+struct NullBackend final : public BackendBase {
+    NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(NullBackend)
+};
+
+int NullBackend::mixerProc()
+{
+    const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
+
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    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))
+    {
+        auto now = std::chrono::steady_clock::now();
+
+        /* This converts from nanoseconds to nanosamples, then to samples. */
+        int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()};
+        if(avail-done < mDevice->UpdateSize)
+        {
+            std::this_thread::sleep_for(restTime);
+            continue;
+        }
+        while(avail-done >= mDevice->UpdateSize)
+        {
+            mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u);
+            done += mDevice->UpdateSize;
+        }
+
+        /* For every completed second, increment the start time and reduce the
+         * samples done. This prevents the difference between the start time
+         * and current time from growing too large, while maintaining the
+         * correct number of samples to render.
+         */
+        if(done >= mDevice->Frequency)
+        {
+            seconds s{done/mDevice->Frequency};
+            start += s;
+            done -= mDevice->Frequency*s.count();
+        }
+    }
+
+    return 0;
+}
+
+
+void NullBackend::open(const char *name)
+{
+    if(!name)
+        name = nullDevice;
+    else if(strcmp(name, nullDevice) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    mDevice->DeviceName = name;
+}
+
+bool NullBackend::reset()
+{
+    setDefaultWFXChannelOrder();
+    return true;
+}
+
+void NullBackend::start()
+{
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void NullBackend::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+}
+
+} // namespace
+
+
+bool NullBackendFactory::init()
+{ return true; }
+
+bool NullBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback); }
+
+std::string NullBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+        /* Includes null char. */
+        outnames.append(nullDevice, sizeof(nullDevice));
+        break;
+    case BackendType::Capture:
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new NullBackend{device}};
+    return nullptr;
+}
+
+BackendFactory &NullBackendFactory::getFactory()
+{
+    static NullBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/null.h

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

+ 250 - 0
Engine/lib/openal-soft/Alc/backends/oboe.cpp

@@ -0,0 +1,250 @@
+
+#include "config.h"
+
+#include "oboe.h"
+
+#include <cassert>
+#include <cstring>
+
+#include "alu.h"
+#include "core/logging.h"
+
+#include "oboe/Oboe.h"
+
+
+namespace {
+
+constexpr char device_name[] = "Oboe Default";
+
+
+struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
+    OboePlayback(ALCdevice *device) : BackendBase{device} { }
+
+    oboe::ManagedStream mStream;
+
+    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+        int32_t numFrames) override;
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+};
+
+
+oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+    int32_t numFrames)
+{
+    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;
+}
+
+
+void OboePlayback::open(const char *name)
+{
+    if(!name)
+        name = device_name;
+    else if(std::strcmp(name, device_name) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    /* Open a basic output stream, just to ensure it can work. */
+    oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
+        ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+        ->openManagedStream(mStream)};
+    if(result != oboe::Result::OK)
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
+            oboe::convertToText(result)};
+
+    mDevice->DeviceName = name;
+}
+
+bool OboePlayback::reset()
+{
+    oboe::AudioStreamBuilder builder;
+    builder.setDirection(oboe::Direction::Output);
+    builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
+    /* Don't let Oboe convert. We should be able to handle anything it gives
+     * back.
+     */
+    builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
+    builder.setChannelConversionAllowed(false);
+    builder.setFormatConversionAllowed(false);
+    builder.setCallback(this);
+
+    if(mDevice->Flags.test(FrequencyRequest))
+        builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
+    if(mDevice->Flags.test(ChannelsRequest))
+    {
+        /* Only use mono or stereo at user request. There's no telling what
+         * other counts may be inferred as.
+         */
+        builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
+            : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
+            : oboe::ChannelCount::Unspecified);
+    }
+    if(mDevice->Flags.test(SampleTypeRequest))
+    {
+        oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
+        switch(mDevice->FmtType)
+        {
+        case DevFmtByte:
+        case DevFmtUByte:
+        case DevFmtShort:
+        case DevFmtUShort:
+            format = oboe::AudioFormat::I16;
+            break;
+        case DevFmtInt:
+        case DevFmtUInt:
+        case DevFmtFloat:
+            format = oboe::AudioFormat::Float;
+            break;
+        }
+        builder.setFormat(format);
+    }
+
+    oboe::Result result{builder.openManagedStream(mStream)};
+    /* If the format failed, try asking for the defaults. */
+    while(result == oboe::Result::ErrorInvalidFormat)
+    {
+        if(builder.getFormat() != oboe::AudioFormat::Unspecified)
+            builder.setFormat(oboe::AudioFormat::Unspecified);
+        else if(builder.getSampleRate() != oboe::kUnspecified)
+            builder.setSampleRate(oboe::kUnspecified);
+        else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
+            builder.setChannelCount(oboe::ChannelCount::Unspecified);
+        else
+            break;
+        result = builder.openManagedStream(mStream);
+    }
+    if(result != oboe::Result::OK)
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
+            oboe::convertToText(result)};
+    TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
+
+    switch(mStream->getChannelCount())
+    {
+    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)
+            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();
+
+    switch(mStream->getFormat())
+    {
+    case oboe::AudioFormat::I16:
+        mDevice->FmtType = DevFmtShort;
+        break;
+    case oboe::AudioFormat::Float:
+        mDevice->FmtType = DevFmtFloat;
+        break;
+    case oboe::AudioFormat::Unspecified:
+    case oboe::AudioFormat::Invalid:
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
+    }
+    mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
+
+    /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
+     * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
+     * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
+     * update size.
+     */
+    mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
+        static_cast<uint32_t>(mStream->getFramesPerBurst()));
+    mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
+        static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
+
+    return true;
+}
+
+void OboePlayback::start()
+{
+    const oboe::Result result{mStream->start()};
+    if(result != oboe::Result::OK)
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
+            oboe::convertToText(result)};
+}
+
+void OboePlayback::stop()
+{
+    oboe::Result result{mStream->stop()};
+    if(result != oboe::Result::OK)
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
+            oboe::convertToText(result)};
+}
+
+} // namespace
+
+bool OboeBackendFactory::init() { return true; }
+
+bool OboeBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback; }
+
+std::string OboeBackendFactory::probe(BackendType type)
+{
+    switch(type)
+    {
+    case BackendType::Playback:
+        /* Includes null char. */
+        return std::string{device_name, sizeof(device_name)};
+    case BackendType::Capture:
+        break;
+    }
+    return std::string{};
+}
+
+BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new OboePlayback{device}};
+    return nullptr;
+}
+
+BackendFactory &OboeBackendFactory::getFactory()
+{
+    static OboeBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/oboe.h

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

+ 0 - 1074
Engine/lib/openal-soft/Alc/backends/opensl.c

@@ -1,1074 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* This is an OpenAL backend for Android using the native audio APIs based on
- * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
- * bundled with NDK.
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <jni.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <SLES/OpenSLES.h>
-#include <SLES/OpenSLES_Android.h>
-#include <SLES/OpenSLES_AndroidConfiguration.h>
-
-/* Helper macros */
-#define VCALL(obj, func)  ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
-#define VCALL0(obj, func)  ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
-
-
-static const ALCchar opensl_device[] = "OpenSL";
-
-
-static SLuint32 GetChannelMask(enum DevFmtChannels chans)
-{
-    switch(chans)
-    {
-        case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
-        case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT;
-        case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
-                                SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT;
-        case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
-                               SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
-                               SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
-        case 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;
-        case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
-                               SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
-                               SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT|
-                               SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
-        case DevFmtAmbi3D:
-            break;
-    }
-    return 0;
-}
-
-#ifdef SL_DATAFORMAT_PCM_EX
-static SLuint32 GetTypeRepresentation(enum DevFmtType type)
-{
-    switch(type)
-    {
-        case DevFmtUByte:
-        case DevFmtUShort:
-        case DevFmtUInt:
-            return SL_PCM_REPRESENTATION_UNSIGNED_INT;
-        case DevFmtByte:
-        case DevFmtShort:
-        case DevFmtInt:
-            return SL_PCM_REPRESENTATION_SIGNED_INT;
-        case DevFmtFloat:
-            return SL_PCM_REPRESENTATION_FLOAT;
-    }
-    return 0;
-}
-#endif
-
-static const char *res_str(SLresult result)
-{
-    switch(result)
-    {
-        case SL_RESULT_SUCCESS: return "Success";
-        case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
-        case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
-        case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
-        case SL_RESULT_RESOURCE_ERROR: return "Resource error";
-        case SL_RESULT_RESOURCE_LOST: return "Resource lost";
-        case SL_RESULT_IO_ERROR: return "I/O error";
-        case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
-        case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
-        case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
-        case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
-        case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
-        case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
-        case SL_RESULT_INTERNAL_ERROR: return "Internal error";
-        case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
-        case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
-        case SL_RESULT_CONTROL_LOST: return "Control lost";
-#ifdef SL_RESULT_READONLY
-        case SL_RESULT_READONLY: return "ReadOnly";
-#endif
-#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
-        case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
-#endif
-#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
-        case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
-#endif
-    }
-    return "Unknown error code";
-}
-
-#define PRINTERR(x, s) do {                                                      \
-    if((x) != SL_RESULT_SUCCESS)                                                 \
-        ERR("%s: %s\n", (s), res_str((x)));                                      \
-} while(0)
-
-
-typedef struct ALCopenslPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    /* engine interfaces */
-    SLObjectItf mEngineObj;
-    SLEngineItf mEngine;
-
-    /* output mix interfaces */
-    SLObjectItf mOutputMix;
-
-    /* buffer queue player interfaces */
-    SLObjectItf mBufferQueueObj;
-
-    ll_ringbuffer_t *mRing;
-    alsem_t mSem;
-
-    ALsizei mFrameSize;
-
-    ATOMIC(ALenum) mKillNow;
-    althrd_t mThread;
-} ALCopenslPlayback;
-
-static void ALCopenslPlayback_process(SLAndroidSimpleBufferQueueItf bq, void *context);
-static int ALCopenslPlayback_mixerProc(void *arg);
-
-static void ALCopenslPlayback_Construct(ALCopenslPlayback *self, ALCdevice *device);
-static void ALCopenslPlayback_Destruct(ALCopenslPlayback *self);
-static ALCenum ALCopenslPlayback_open(ALCopenslPlayback *self, const ALCchar *name);
-static ALCboolean ALCopenslPlayback_reset(ALCopenslPlayback *self);
-static ALCboolean ALCopenslPlayback_start(ALCopenslPlayback *self);
-static void ALCopenslPlayback_stop(ALCopenslPlayback *self);
-static DECLARE_FORWARD2(ALCopenslPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCopenslPlayback, ALCbackend, ALCuint, availableSamples)
-static ClockLatency ALCopenslPlayback_getClockLatency(ALCopenslPlayback *self);
-static DECLARE_FORWARD(ALCopenslPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCopenslPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCopenslPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCopenslPlayback);
-
-
-static void ALCopenslPlayback_Construct(ALCopenslPlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCopenslPlayback, ALCbackend, self);
-
-    self->mEngineObj = NULL;
-    self->mEngine = NULL;
-    self->mOutputMix = NULL;
-    self->mBufferQueueObj = NULL;
-
-    self->mRing = NULL;
-    alsem_init(&self->mSem, 0);
-
-    self->mFrameSize = 0;
-
-    ATOMIC_INIT(&self->mKillNow, AL_FALSE);
-}
-
-static void ALCopenslPlayback_Destruct(ALCopenslPlayback* self)
-{
-    if(self->mBufferQueueObj != NULL)
-        VCALL0(self->mBufferQueueObj,Destroy)();
-    self->mBufferQueueObj = NULL;
-
-    if(self->mOutputMix)
-        VCALL0(self->mOutputMix,Destroy)();
-    self->mOutputMix = NULL;
-
-    if(self->mEngineObj)
-        VCALL0(self->mEngineObj,Destroy)();
-    self->mEngineObj = NULL;
-    self->mEngine = NULL;
-
-    alsem_destroy(&self->mSem);
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-/* this callback handler is called every time a buffer finishes playing */
-static void ALCopenslPlayback_process(SLAndroidSimpleBufferQueueItf UNUSED(bq), void *context)
-{
-    ALCopenslPlayback *self = context;
-
-    /* A note on the ringbuffer usage: The buffer queue seems to hold on to the
-     * pointer passed to the Enqueue method, rather than copying the audio.
-     * Consequently, the ringbuffer contains the audio that is currently queued
-     * and waiting to play. This process() callback is called when a buffer is
-     * finished, so we simply move the read pointer up to indicate the space is
-     * available for writing again, and wake up the mixer thread to mix and
-     * queue more audio.
-     */
-    ll_ringbuffer_read_advance(self->mRing, 1);
-
-    alsem_post(&self->mSem);
-}
-
-
-static int ALCopenslPlayback_mixerProc(void *arg)
-{
-    ALCopenslPlayback *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    SLAndroidSimpleBufferQueueItf bufferQueue;
-    ll_ringbuffer_data_t data[2];
-    SLPlayItf player;
-    SLresult result;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                                                       &bufferQueue);
-    PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
-        PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
-    }
-    if(SL_RESULT_SUCCESS != result)
-    {
-        ALCopenslPlayback_lock(self);
-        aluHandleDisconnect(device, "Failed to get playback buffer: 0x%08x", result);
-        ALCopenslPlayback_unlock(self);
-        return 1;
-    }
-
-    ALCopenslPlayback_lock(self);
-    while(!ATOMIC_LOAD(&self->mKillNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        size_t todo, len0, len1;
-
-        if(ll_ringbuffer_write_space(self->mRing) == 0)
-        {
-            SLuint32 state = 0;
-
-            result = VCALL(player,GetPlayState)(&state);
-            PRINTERR(result, "player->GetPlayState");
-            if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
-            {
-                result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
-                PRINTERR(result, "player->SetPlayState");
-            }
-            if(SL_RESULT_SUCCESS != result)
-            {
-                aluHandleDisconnect(device, "Failed to start platback: 0x%08x", result);
-                break;
-            }
-
-            if(ll_ringbuffer_write_space(self->mRing) == 0)
-            {
-                ALCopenslPlayback_unlock(self);
-                alsem_wait(&self->mSem);
-                ALCopenslPlayback_lock(self);
-                continue;
-            }
-        }
-
-        ll_ringbuffer_get_write_vector(self->mRing, data);
-        todo = data[0].len+data[1].len;
-
-        len0 = minu(todo, data[0].len);
-        len1 = minu(todo-len0, data[1].len);
-
-        aluMixData(device, data[0].buf, len0*device->UpdateSize);
-        for(size_t i = 0;i < len0;i++)
-        {
-            result = VCALL(bufferQueue,Enqueue)(data[0].buf, device->UpdateSize*self->mFrameSize);
-            PRINTERR(result, "bufferQueue->Enqueue");
-            if(SL_RESULT_SUCCESS == result)
-                ll_ringbuffer_write_advance(self->mRing, 1);
-
-            data[0].buf += device->UpdateSize*self->mFrameSize;
-        }
-
-        if(len1 > 0)
-        {
-            aluMixData(device, data[1].buf, len1*device->UpdateSize);
-            for(size_t i = 0;i < len1;i++)
-            {
-                result = VCALL(bufferQueue,Enqueue)(data[1].buf, device->UpdateSize*self->mFrameSize);
-                PRINTERR(result, "bufferQueue->Enqueue");
-                if(SL_RESULT_SUCCESS == result)
-                    ll_ringbuffer_write_advance(self->mRing, 1);
-
-                data[1].buf += device->UpdateSize*self->mFrameSize;
-            }
-        }
-    }
-    ALCopenslPlayback_unlock(self);
-
-    return 0;
-}
-
-
-static ALCenum ALCopenslPlayback_open(ALCopenslPlayback *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    SLresult result;
-
-    if(!name)
-        name = opensl_device;
-    else if(strcmp(name, opensl_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    // create engine
-    result = slCreateEngine(&self->mEngineObj, 0, NULL, 0, NULL, NULL);
-    PRINTERR(result, "slCreateEngine");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mEngineObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "engine->Realize");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mEngineObj,GetInterface)(SL_IID_ENGINE, &self->mEngine);
-        PRINTERR(result, "engine->GetInterface");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mEngine,CreateOutputMix)(&self->mOutputMix, 0, NULL, NULL);
-        PRINTERR(result, "engine->CreateOutputMix");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mOutputMix,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "outputMix->Realize");
-    }
-
-    if(SL_RESULT_SUCCESS != result)
-    {
-        if(self->mOutputMix != NULL)
-            VCALL0(self->mOutputMix,Destroy)();
-        self->mOutputMix = NULL;
-
-        if(self->mEngineObj != NULL)
-            VCALL0(self->mEngineObj,Destroy)();
-        self->mEngineObj = NULL;
-        self->mEngine = NULL;
-
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCopenslPlayback_reset(ALCopenslPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
-    SLDataLocator_OutputMix loc_outmix;
-    SLDataSource audioSrc;
-    SLDataSink audioSnk;
-    ALuint sampleRate;
-    SLInterfaceID ids[2];
-    SLboolean reqs[2];
-    SLresult result;
-    JNIEnv *env;
-
-    if(self->mBufferQueueObj != NULL)
-        VCALL0(self->mBufferQueueObj,Destroy)();
-    self->mBufferQueueObj = NULL;
-
-    sampleRate = device->Frequency;
-    if(!(device->Flags&DEVICE_FREQUENCY_REQUEST) && (env=Android_GetJNIEnv()) != NULL)
-    {
-        /* FIXME: Disabled until I figure out how to get the Context needed for
-         * the getSystemService call.
-         */
-#if 0
-        /* Get necessary stuff for using java.lang.Integer,
-         * android.content.Context, and android.media.AudioManager.
-         */
-        jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
-        jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
-            "parseInt", "(Ljava/lang/String;)I"
-        );
-        TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
-
-        jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
-        jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
-            "AUDIO_SERVICE", "Ljava/lang/String;"
-        );
-        jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
-            "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
-        );
-        TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
-              ctx_cls, ctx_audsvc, ctx_getSysSvc);
-
-        jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
-        jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
-            "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
-        );
-        jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
-            "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
-        );
-        TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
-              audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
-
-        const char *strchars;
-        jstring strobj;
-
-        /* Now make the calls. */
-        //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
-        strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
-        jobject audMgr = JCALL(env,CallObjectMethod)(ctx_cls, ctx_getSysSvc, strobj);
-        strchars = JCALL(env,GetStringUTFChars)(strobj, NULL);
-        TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
-        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
-
-        //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
-        strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
-        jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
-        strchars = JCALL(env,GetStringUTFChars)(strobj, NULL);
-        TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
-        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
-
-        //int sampleRate = Integer.parseInt(srateStr);
-        sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
-
-        strchars = JCALL(env,GetStringUTFChars)(srateStr, NULL);
-        TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
-        JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
-
-        if(!sampleRate) sampleRate = device->Frequency;
-        else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
-#endif
-    }
-
-    if(sampleRate != device->Frequency)
-    {
-        device->NumUpdates = (device->NumUpdates*sampleRate + (device->Frequency>>1)) /
-                             device->Frequency;
-        device->NumUpdates = maxu(device->NumUpdates, 2);
-        device->Frequency = sampleRate;
-    }
-
-    device->FmtChans = DevFmtStereo;
-    device->FmtType = DevFmtShort;
-
-    SetDefaultWFXChannelOrder(device);
-    self->mFrameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-
-    loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
-    loc_bufq.numBuffers = device->NumUpdates;
-
-#ifdef SL_DATAFORMAT_PCM_EX
-    SLDataFormat_PCM_EX format_pcm;
-    format_pcm.formatType = SL_DATAFORMAT_PCM_EX;
-    format_pcm.numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    format_pcm.sampleRate = device->Frequency * 1000;
-    format_pcm.bitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    format_pcm.containerSize = format_pcm.bitsPerSample;
-    format_pcm.channelMask = GetChannelMask(device->FmtChans);
-    format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
-                                               SL_BYTEORDER_BIGENDIAN;
-    format_pcm.representation = GetTypeRepresentation(device->FmtType);
-#else
-    SLDataFormat_PCM format_pcm;
-    format_pcm.formatType = SL_DATAFORMAT_PCM;
-    format_pcm.numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    format_pcm.samplesPerSec = device->Frequency * 1000;
-    format_pcm.bitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    format_pcm.containerSize = format_pcm.bitsPerSample;
-    format_pcm.channelMask = GetChannelMask(device->FmtChans);
-    format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
-                                               SL_BYTEORDER_BIGENDIAN;
-#endif
-
-    audioSrc.pLocator = &loc_bufq;
-    audioSrc.pFormat = &format_pcm;
-
-    loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
-    loc_outmix.outputMix = self->mOutputMix;
-    audioSnk.pLocator = &loc_outmix;
-    audioSnk.pFormat = NULL;
-
-
-    ids[0]  = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
-    reqs[0] = SL_BOOLEAN_TRUE;
-    ids[1]  = SL_IID_ANDROIDCONFIGURATION;
-    reqs[1] = SL_BOOLEAN_FALSE;
-
-    result = VCALL(self->mEngine,CreateAudioPlayer)(&self->mBufferQueueObj,
-        &audioSrc, &audioSnk, COUNTOF(ids), ids, reqs
-    );
-    PRINTERR(result, "engine->CreateAudioPlayer");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        /* Set the stream type to "media" (games, music, etc), if possible. */
-        SLAndroidConfigurationItf config;
-        result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
-        PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
-        if(SL_RESULT_SUCCESS == result)
-        {
-            SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
-            result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE,
-                &streamType, sizeof(streamType)
-            );
-            PRINTERR(result, "config->SetConfiguration");
-        }
-
-        /* Clear any error since this was optional. */
-        result = SL_RESULT_SUCCESS;
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "bufferQueue->Realize");
-    }
-
-    if(SL_RESULT_SUCCESS != result)
-    {
-        if(self->mBufferQueueObj != NULL)
-            VCALL0(self->mBufferQueueObj,Destroy)();
-        self->mBufferQueueObj = NULL;
-
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCopenslPlayback_start(ALCopenslPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    SLAndroidSimpleBufferQueueItf bufferQueue;
-    SLresult result;
-
-    ll_ringbuffer_free(self->mRing);
-    self->mRing = ll_ringbuffer_create(device->NumUpdates, self->mFrameSize*device->UpdateSize,
-                                       true);
-
-    result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                                                       &bufferQueue);
-    PRINTERR(result, "bufferQueue->GetInterface");
-    if(SL_RESULT_SUCCESS != result)
-        return ALC_FALSE;
-
-    result = VCALL(bufferQueue,RegisterCallback)(ALCopenslPlayback_process, self);
-    PRINTERR(result, "bufferQueue->RegisterCallback");
-    if(SL_RESULT_SUCCESS != result)
-        return ALC_FALSE;
-
-    ATOMIC_STORE_SEQ(&self->mKillNow, AL_FALSE);
-    if(althrd_create(&self->mThread, ALCopenslPlayback_mixerProc, self) != althrd_success)
-    {
-        ERR("Failed to start mixer thread\n");
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-
-static void ALCopenslPlayback_stop(ALCopenslPlayback *self)
-{
-    SLAndroidSimpleBufferQueueItf bufferQueue;
-    SLPlayItf player;
-    SLresult result;
-    int res;
-
-    if(ATOMIC_EXCHANGE_SEQ(&self->mKillNow, AL_TRUE))
-        return;
-
-    alsem_post(&self->mSem);
-    althrd_join(self->mThread, &res);
-
-    result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
-    PRINTERR(result, "bufferQueue->GetInterface");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
-        PRINTERR(result, "player->SetPlayState");
-    }
-
-    result = VCALL(self->mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                                                       &bufferQueue);
-    PRINTERR(result, "bufferQueue->GetInterface");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL0(bufferQueue,Clear)();
-        PRINTERR(result, "bufferQueue->Clear");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(bufferQueue,RegisterCallback)(NULL, NULL);
-        PRINTERR(result, "bufferQueue->RegisterCallback");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        SLAndroidSimpleBufferQueueState state;
-        do {
-            althrd_yield();
-            result = VCALL(bufferQueue,GetState)(&state);
-        } while(SL_RESULT_SUCCESS == result && state.count > 0);
-        PRINTERR(result, "bufferQueue->GetState");
-    }
-
-    ll_ringbuffer_free(self->mRing);
-    self->mRing = NULL;
-}
-
-static ClockLatency ALCopenslPlayback_getClockLatency(ALCopenslPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ClockLatency ret;
-
-    ALCopenslPlayback_lock(self);
-    ret.ClockTime = GetDeviceClockTime(device);
-    ret.Latency = ll_ringbuffer_read_space(self->mRing)*device->UpdateSize *
-                  DEVICE_CLOCK_RES / device->Frequency;
-    ALCopenslPlayback_unlock(self);
-
-    return ret;
-}
-
-
-typedef struct ALCopenslCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    /* engine interfaces */
-    SLObjectItf mEngineObj;
-    SLEngineItf mEngine;
-
-    /* recording interfaces */
-    SLObjectItf mRecordObj;
-
-    ll_ringbuffer_t *mRing;
-    ALCuint mSplOffset;
-
-    ALsizei mFrameSize;
-} ALCopenslCapture;
-
-static void ALCopenslCapture_process(SLAndroidSimpleBufferQueueItf bq, void *context);
-
-static void ALCopenslCapture_Construct(ALCopenslCapture *self, ALCdevice *device);
-static void ALCopenslCapture_Destruct(ALCopenslCapture *self);
-static ALCenum ALCopenslCapture_open(ALCopenslCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCopenslCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCopenslCapture_start(ALCopenslCapture *self);
-static void ALCopenslCapture_stop(ALCopenslCapture *self);
-static ALCenum ALCopenslCapture_captureSamples(ALCopenslCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCopenslCapture_availableSamples(ALCopenslCapture *self);
-static DECLARE_FORWARD(ALCopenslCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCopenslCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCopenslCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCopenslCapture)
-DEFINE_ALCBACKEND_VTABLE(ALCopenslCapture);
-
-
-static void ALCopenslCapture_process(SLAndroidSimpleBufferQueueItf UNUSED(bq), void *context)
-{
-    ALCopenslCapture *self = context;
-    /* A new chunk has been written into the ring buffer, advance it. */
-    ll_ringbuffer_write_advance(self->mRing, 1);
-}
-
-
-static void ALCopenslCapture_Construct(ALCopenslCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCopenslCapture, ALCbackend, self);
-
-    self->mEngineObj = NULL;
-    self->mEngine = NULL;
-
-    self->mRecordObj = NULL;
-
-    self->mRing = NULL;
-    self->mSplOffset = 0;
-
-    self->mFrameSize = 0;
-}
-
-static void ALCopenslCapture_Destruct(ALCopenslCapture *self)
-{
-    ll_ringbuffer_free(self->mRing);
-    self->mRing = NULL;
-
-    if(self->mRecordObj != NULL)
-        VCALL0(self->mRecordObj,Destroy)();
-    self->mRecordObj = NULL;
-
-    if(self->mEngineObj != NULL)
-        VCALL0(self->mEngineObj,Destroy)();
-    self->mEngineObj = NULL;
-    self->mEngine = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static ALCenum ALCopenslCapture_open(ALCopenslCapture *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    SLDataLocator_AndroidSimpleBufferQueue loc_bq;
-    SLAndroidSimpleBufferQueueItf bufferQueue;
-    SLDataLocator_IODevice loc_dev;
-    SLDataSource audioSrc;
-    SLDataSink audioSnk;
-    SLresult result;
-
-    if(!name)
-        name = opensl_device;
-    else if(strcmp(name, opensl_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    result = slCreateEngine(&self->mEngineObj, 0, NULL, 0, NULL, NULL);
-    PRINTERR(result, "slCreateEngine");
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mEngineObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "engine->Realize");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mEngineObj,GetInterface)(SL_IID_ENGINE, &self->mEngine);
-        PRINTERR(result, "engine->GetInterface");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        /* Ensure the total length is at least 100ms */
-        ALsizei length = maxi(device->NumUpdates * device->UpdateSize,
-                              device->Frequency / 10);
-        /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
-        ALsizei update_len = clampi(device->NumUpdates*device->UpdateSize / 3,
-                                    device->Frequency / 100,
-                                    device->Frequency / 100 * 5);
-
-        device->UpdateSize = update_len;
-        device->NumUpdates = (length+update_len-1) / update_len;
-
-        self->mFrameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    }
-    loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
-    loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
-    loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
-    loc_dev.device = NULL;
-
-    audioSrc.pLocator = &loc_dev;
-    audioSrc.pFormat = NULL;
-
-    loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
-    loc_bq.numBuffers = device->NumUpdates;
-
-#ifdef SL_DATAFORMAT_PCM_EX
-    SLDataFormat_PCM_EX format_pcm;
-    format_pcm.formatType = SL_DATAFORMAT_PCM_EX;
-    format_pcm.numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    format_pcm.sampleRate = device->Frequency * 1000;
-    format_pcm.bitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    format_pcm.containerSize = format_pcm.bitsPerSample;
-    format_pcm.channelMask = GetChannelMask(device->FmtChans);
-    format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
-                                               SL_BYTEORDER_BIGENDIAN;
-    format_pcm.representation = GetTypeRepresentation(device->FmtType);
-#else
-    SLDataFormat_PCM format_pcm;
-    format_pcm.formatType = SL_DATAFORMAT_PCM;
-    format_pcm.numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    format_pcm.samplesPerSec = device->Frequency * 1000;
-    format_pcm.bitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    format_pcm.containerSize = format_pcm.bitsPerSample;
-    format_pcm.channelMask = GetChannelMask(device->FmtChans);
-    format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
-                                               SL_BYTEORDER_BIGENDIAN;
-#endif
-
-    audioSnk.pLocator = &loc_bq;
-    audioSnk.pFormat = &format_pcm;
-
-    if(SL_RESULT_SUCCESS == result)
-    {
-        const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
-        const SLboolean reqs[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
-
-        result = VCALL(self->mEngine,CreateAudioRecorder)(&self->mRecordObj,
-            &audioSrc, &audioSnk, COUNTOF(ids), ids, reqs
-        );
-        PRINTERR(result, "engine->CreateAudioRecorder");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        /* Set the record preset to "generic", if possible. */
-        SLAndroidConfigurationItf config;
-        result = VCALL(self->mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
-        PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
-        if(SL_RESULT_SUCCESS == result)
-        {
-            SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
-            result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET,
-                &preset, sizeof(preset)
-            );
-            PRINTERR(result, "config->SetConfiguration");
-        }
-
-        /* Clear any error since this was optional. */
-        result = SL_RESULT_SUCCESS;
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(self->mRecordObj,Realize)(SL_BOOLEAN_FALSE);
-        PRINTERR(result, "recordObj->Realize");
-    }
-
-    if(SL_RESULT_SUCCESS == result)
-    {
-        self->mRing = ll_ringbuffer_create(device->NumUpdates, device->UpdateSize*self->mFrameSize,
-                                           false);
-
-        result = VCALL(self->mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                                                      &bufferQueue);
-        PRINTERR(result, "recordObj->GetInterface");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(bufferQueue,RegisterCallback)(ALCopenslCapture_process, self);
-        PRINTERR(result, "bufferQueue->RegisterCallback");
-    }
-    if(SL_RESULT_SUCCESS == result)
-    {
-        ALsizei chunk_size = device->UpdateSize * self->mFrameSize;
-        ll_ringbuffer_data_t data[2];
-        size_t i;
-
-        ll_ringbuffer_get_write_vector(self->mRing, data);
-        for(i = 0;i < data[0].len && SL_RESULT_SUCCESS == result;i++)
-        {
-            result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
-        }
-        for(i = 0;i < data[1].len && SL_RESULT_SUCCESS == result;i++)
-        {
-            result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size);
-            PRINTERR(result, "bufferQueue->Enqueue");
-        }
-    }
-
-    if(SL_RESULT_SUCCESS != result)
-    {
-        if(self->mRecordObj != NULL)
-            VCALL0(self->mRecordObj,Destroy)();
-        self->mRecordObj = NULL;
-
-        if(self->mEngineObj != NULL)
-            VCALL0(self->mEngineObj,Destroy)();
-        self->mEngineObj = NULL;
-        self->mEngine = NULL;
-
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCopenslCapture_start(ALCopenslCapture *self)
-{
-    SLRecordItf record;
-    SLresult result;
-
-    result = VCALL(self->mRecordObj,GetInterface)(SL_IID_RECORD, &record);
-    PRINTERR(result, "recordObj->GetInterface");
-
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
-        PRINTERR(result, "record->SetRecordState");
-    }
-
-    if(SL_RESULT_SUCCESS != result)
-    {
-        ALCopenslCapture_lock(self);
-        aluHandleDisconnect(STATIC_CAST(ALCbackend, self)->mDevice,
-                            "Failed to start capture: 0x%08x", result);
-        ALCopenslCapture_unlock(self);
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCopenslCapture_stop(ALCopenslCapture *self)
-{
-    SLRecordItf record;
-    SLresult result;
-
-    result = VCALL(self->mRecordObj,GetInterface)(SL_IID_RECORD, &record);
-    PRINTERR(result, "recordObj->GetInterface");
-
-    if(SL_RESULT_SUCCESS == result)
-    {
-        result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
-        PRINTERR(result, "record->SetRecordState");
-    }
-}
-
-static ALCenum ALCopenslCapture_captureSamples(ALCopenslCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALsizei chunk_size = device->UpdateSize * self->mFrameSize;
-    SLAndroidSimpleBufferQueueItf bufferQueue;
-    ll_ringbuffer_data_t data[2];
-    SLresult result;
-    size_t advance;
-    ALCuint i;
-
-    /* Read the desired samples from the ring buffer then advance its read
-     * pointer.
-     */
-    ll_ringbuffer_get_read_vector(self->mRing, data);
-    advance = 0;
-    for(i = 0;i < samples;)
-    {
-        ALCuint rem = minu(samples - i, device->UpdateSize - self->mSplOffset);
-        memcpy((ALCbyte*)buffer + i*self->mFrameSize,
-               data[0].buf + self->mSplOffset*self->mFrameSize,
-               rem * self->mFrameSize);
-
-        self->mSplOffset += rem;
-        if(self->mSplOffset == device->UpdateSize)
-        {
-            /* Finished a chunk, reset the offset and advance the read pointer. */
-            self->mSplOffset = 0;
-            advance++;
-
-            data[0].len--;
-            if(!data[0].len)
-                data[0] = data[1];
-            else
-                data[0].buf += chunk_size;
-        }
-
-        i += rem;
-    }
-    ll_ringbuffer_read_advance(self->mRing, advance);
-
-    result = VCALL(self->mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                                                  &bufferQueue);
-    PRINTERR(result, "recordObj->GetInterface");
-
-    /* Enqueue any newly-writable chunks in the ring buffer. */
-    ll_ringbuffer_get_write_vector(self->mRing, data);
-    for(i = 0;i < data[0].len && SL_RESULT_SUCCESS == result;i++)
-    {
-        result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size);
-        PRINTERR(result, "bufferQueue->Enqueue");
-    }
-    for(i = 0;i < data[1].len && SL_RESULT_SUCCESS == result;i++)
-    {
-        result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size);
-        PRINTERR(result, "bufferQueue->Enqueue");
-    }
-
-    if(SL_RESULT_SUCCESS != result)
-    {
-        ALCopenslCapture_lock(self);
-        aluHandleDisconnect(device, "Failed to update capture buffer: 0x%08x", result);
-        ALCopenslCapture_unlock(self);
-        return ALC_INVALID_DEVICE;
-    }
-
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCopenslCapture_availableSamples(ALCopenslCapture *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    return ll_ringbuffer_read_space(self->mRing) * device->UpdateSize;
-}
-
-
-typedef struct ALCopenslBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCopenslBackendFactory;
-#define ALCOPENSLBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCopenslBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCopenslBackendFactory_init(ALCopenslBackendFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static void ALCopenslBackendFactory_deinit(ALCopenslBackendFactory* UNUSED(self))
-{
-}
-
-static ALCboolean ALCopenslBackendFactory_querySupport(ALCopenslBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCopenslBackendFactory_probe(ALCopenslBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(opensl_device);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            AppendAllDevicesList(opensl_device);
-            break;
-    }
-}
-
-static ALCbackend* ALCopenslBackendFactory_createBackend(ALCopenslBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCopenslPlayback *backend;
-        NEW_OBJ(backend, ALCopenslPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCopenslCapture *backend;
-        NEW_OBJ(backend, ALCopenslCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCopenslBackendFactory);
-
-
-ALCbackendFactory *ALCopenslBackendFactory_getFactory(void)
-{
-    static ALCopenslBackendFactory factory = ALCOPENSLBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 975 - 0
Engine/lib/openal-soft/Alc/backends/opensl.cpp

@@ -0,0 +1,975 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This is an OpenAL backend for Android using the native audio APIs based on
+ * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
+ * bundled with NDK.
+ */
+
+#include "config.h"
+
+#include "backends/opensl.h"
+
+#include <stdlib.h>
+#include <jni.h>
+
+#include <new>
+#include <array>
+#include <cstring>
+#include <thread>
+#include <functional>
+
+#include "albit.h"
+#include "alcmain.h"
+#include "alu.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <SLES/OpenSLES_AndroidConfiguration.h>
+
+
+namespace {
+
+/* Helper macros */
+#define EXTRACT_VCALL_ARGS(...)  __VA_ARGS__))
+#define VCALL(obj, func)  ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
+#define VCALL0(obj, func)  ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
+
+
+constexpr char opensl_device[] = "OpenSL";
+
+
+constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
+{
+    switch(chans)
+    {
+    case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
+    case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+    case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+        SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
+    case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+        SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
+        SL_SPEAKER_SIDE_RIGHT;
+    case 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;
+    case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+        SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
+        SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
+    case DevFmtAmbi3D:
+        break;
+    }
+    return 0;
+}
+
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
+{
+    switch(type)
+    {
+    case DevFmtUByte:
+    case DevFmtUShort:
+    case DevFmtUInt:
+        return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
+    case DevFmtByte:
+    case DevFmtShort:
+    case DevFmtInt:
+        return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+    case DevFmtFloat:
+        return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+    }
+    return 0;
+}
+#endif
+
+constexpr SLuint32 GetByteOrderEndianness() noexcept
+{
+    if_constexpr(al::endian::native == al::endian::little)
+        return SL_BYTEORDER_LITTLEENDIAN;
+    return SL_BYTEORDER_BIGENDIAN;
+}
+
+const char *res_str(SLresult result) noexcept
+{
+    switch(result)
+    {
+    case SL_RESULT_SUCCESS: return "Success";
+    case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
+    case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
+    case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
+    case SL_RESULT_RESOURCE_ERROR: return "Resource error";
+    case SL_RESULT_RESOURCE_LOST: return "Resource lost";
+    case SL_RESULT_IO_ERROR: return "I/O error";
+    case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
+    case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
+    case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
+    case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
+    case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
+    case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
+    case SL_RESULT_INTERNAL_ERROR: return "Internal error";
+    case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
+    case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
+    case SL_RESULT_CONTROL_LOST: return "Control lost";
+#ifdef SL_RESULT_READONLY
+    case SL_RESULT_READONLY: return "ReadOnly";
+#endif
+#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
+    case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
+#endif
+#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
+    case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
+#endif
+    }
+    return "Unknown error code";
+}
+
+#define PRINTERR(x, s) do {                                                      \
+    if UNLIKELY((x) != SL_RESULT_SUCCESS)                                        \
+        ERR("%s: %s\n", (s), res_str((x)));                                      \
+} while(0)
+
+
+struct OpenSLPlayback final : public BackendBase {
+    OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~OpenSLPlayback() override;
+
+    void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
+    static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
+    { static_cast<OpenSLPlayback*>(context)->process(bq); }
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+    ClockLatency getClockLatency() override;
+
+    /* engine interfaces */
+    SLObjectItf mEngineObj{nullptr};
+    SLEngineItf mEngine{nullptr};
+
+    /* output mix interfaces */
+    SLObjectItf mOutputMix{nullptr};
+
+    /* buffer queue player interfaces */
+    SLObjectItf mBufferQueueObj{nullptr};
+
+    RingBufferPtr mRing{nullptr};
+    al::semaphore mSem;
+
+    std::mutex mMutex;
+
+    uint mFrameSize{0};
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(OpenSLPlayback)
+};
+
+OpenSLPlayback::~OpenSLPlayback()
+{
+    if(mBufferQueueObj)
+        VCALL0(mBufferQueueObj,Destroy)();
+    mBufferQueueObj = nullptr;
+
+    if(mOutputMix)
+        VCALL0(mOutputMix,Destroy)();
+    mOutputMix = nullptr;
+
+    if(mEngineObj)
+        VCALL0(mEngineObj,Destroy)();
+    mEngineObj = nullptr;
+    mEngine = nullptr;
+}
+
+
+/* this callback handler is called every time a buffer finishes playing */
+void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
+{
+    /* A note on the ringbuffer usage: The buffer queue seems to hold on to the
+     * pointer passed to the Enqueue method, rather than copying the audio.
+     * Consequently, the ringbuffer contains the audio that is currently queued
+     * and waiting to play. This process() callback is called when a buffer is
+     * finished, so we simply move the read pointer up to indicate the space is
+     * available for writing again, and wake up the mixer thread to mix and
+     * queue more audio.
+     */
+    mRing->readAdvance(1);
+
+    mSem.post();
+}
+
+int OpenSLPlayback::mixerProc()
+{
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    SLPlayItf player;
+    SLAndroidSimpleBufferQueueItf bufferQueue;
+    SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+        &bufferQueue)};
+    PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
+        PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
+    }
+
+    const size_t frame_step{mDevice->channelsFromFmt()};
+
+    if(SL_RESULT_SUCCESS != result)
+        mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result);
+
+    while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
+    {
+        if(mRing->writeSpace() == 0)
+        {
+            SLuint32 state{0};
+
+            result = VCALL(player,GetPlayState)(&state);
+            PRINTERR(result, "player->GetPlayState");
+            if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
+            {
+                result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
+                PRINTERR(result, "player->SetPlayState");
+            }
+            if(SL_RESULT_SUCCESS != result)
+            {
+                mDevice->handleDisconnect("Failed to start playback: 0x%08x", result);
+                break;
+            }
+
+            if(mRing->writeSpace() == 0)
+            {
+                mSem.wait();
+                continue;
+            }
+        }
+
+        std::unique_lock<std::mutex> dlock{mMutex};
+        auto data = mRing->getWriteVector();
+        mDevice->renderSamples(data.first.buf,
+            static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
+        if(data.second.len > 0)
+            mDevice->renderSamples(data.second.buf,
+                static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
+
+        size_t todo{data.first.len + data.second.len};
+        mRing->writeAdvance(todo);
+        dlock.unlock();
+
+        for(size_t i{0};i < todo;i++)
+        {
+            if(!data.first.len)
+            {
+                data.first = data.second;
+                data.second.buf = nullptr;
+                data.second.len = 0;
+            }
+
+            result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
+            PRINTERR(result, "bufferQueue->Enqueue");
+            if(SL_RESULT_SUCCESS != result)
+            {
+                mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
+                break;
+            }
+
+            data.first.len--;
+            data.first.buf += mDevice->UpdateSize*mFrameSize;
+        }
+    }
+
+    return 0;
+}
+
+
+void OpenSLPlayback::open(const char *name)
+{
+    if(!name)
+        name = opensl_device;
+    else if(strcmp(name, opensl_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    // create engine
+    SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
+    PRINTERR(result, "slCreateEngine");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
+        PRINTERR(result, "engine->Realize");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
+        PRINTERR(result, "engine->GetInterface");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
+        PRINTERR(result, "engine->CreateOutputMix");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
+        PRINTERR(result, "outputMix->Realize");
+    }
+
+    if(SL_RESULT_SUCCESS != result)
+    {
+        if(mOutputMix)
+            VCALL0(mOutputMix,Destroy)();
+        mOutputMix = nullptr;
+
+        if(mEngineObj)
+            VCALL0(mEngineObj,Destroy)();
+        mEngineObj = nullptr;
+        mEngine = nullptr;
+
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to initialize OpenSL device: 0x%08x", result};
+    }
+
+    mDevice->DeviceName = name;
+}
+
+bool OpenSLPlayback::reset()
+{
+    SLresult result;
+
+    if(mBufferQueueObj)
+        VCALL0(mBufferQueueObj,Destroy)();
+    mBufferQueueObj = nullptr;
+
+    mRing = nullptr;
+
+#if 0
+    if(!mDevice->Flags.get<FrequencyRequest>())
+    {
+        /* FIXME: Disabled until I figure out how to get the Context needed for
+         * the getSystemService call.
+         */
+        JNIEnv *env = Android_GetJNIEnv();
+        jobject jctx = Android_GetContext();
+
+        /* Get necessary stuff for using java.lang.Integer,
+         * android.content.Context, and android.media.AudioManager.
+         */
+        jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
+        jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
+            "parseInt", "(Ljava/lang/String;)I"
+        );
+        TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
+
+        jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
+        jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
+            "AUDIO_SERVICE", "Ljava/lang/String;"
+        );
+        jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
+            "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
+        );
+        TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
+              ctx_cls, ctx_audsvc, ctx_getSysSvc);
+
+        jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
+        jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
+            "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
+        );
+        jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
+            "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
+        );
+        TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
+              audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
+
+        const char *strchars;
+        jstring strobj;
+
+        /* Now make the calls. */
+        //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
+        strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
+        jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
+        strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
+        TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
+        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
+
+        //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+        strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
+        jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
+        strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
+        TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
+        JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
+
+        //int sampleRate = Integer.parseInt(srateStr);
+        sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
+
+        strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
+        TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
+        JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
+
+        if(!sampleRate) sampleRate = device->Frequency;
+        else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
+    }
+#endif
+
+    mDevice->FmtChans = DevFmtStereo;
+    mDevice->FmtType = DevFmtShort;
+
+    setDefaultWFXChannelOrder();
+    mFrameSize = mDevice->frameSizeFromFmt();
+
+
+    const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
+    const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
+
+    SLDataLocator_OutputMix loc_outmix{};
+    loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+    loc_outmix.outputMix = mOutputMix;
+
+    SLDataSink audioSnk{};
+    audioSnk.pLocator = &loc_outmix;
+    audioSnk.pFormat = nullptr;
+
+    SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
+    loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+    loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
+
+    SLDataSource audioSrc{};
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+    SLAndroidDataFormat_PCM_EX format_pcm_ex{};
+    format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+    format_pcm_ex.numChannels = mDevice->channelsFromFmt();
+    format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
+    format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
+    format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
+    format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
+    format_pcm_ex.endianness = GetByteOrderEndianness();
+    format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
+
+    audioSrc.pLocator = &loc_bufq;
+    audioSrc.pFormat = &format_pcm_ex;
+
+    result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
+        ids.data(), reqs.data());
+    if(SL_RESULT_SUCCESS != result)
+#endif
+    {
+        /* Alter sample type according to what SLDataFormat_PCM can support. */
+        switch(mDevice->FmtType)
+        {
+        case DevFmtByte: mDevice->FmtType = DevFmtUByte; break;
+        case DevFmtUInt: mDevice->FmtType = DevFmtInt; break;
+        case DevFmtFloat:
+        case DevFmtUShort: mDevice->FmtType = DevFmtShort; break;
+        case DevFmtUByte:
+        case DevFmtShort:
+        case DevFmtInt:
+            break;
+        }
+
+        SLDataFormat_PCM format_pcm{};
+        format_pcm.formatType = SL_DATAFORMAT_PCM;
+        format_pcm.numChannels = mDevice->channelsFromFmt();
+        format_pcm.samplesPerSec = mDevice->Frequency * 1000;
+        format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+        format_pcm.containerSize = format_pcm.bitsPerSample;
+        format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+        format_pcm.endianness = GetByteOrderEndianness();
+
+        audioSrc.pLocator = &loc_bufq;
+        audioSrc.pFormat = &format_pcm;
+
+        result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
+            ids.data(), reqs.data());
+        PRINTERR(result, "engine->CreateAudioPlayer");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        /* Set the stream type to "media" (games, music, etc), if possible. */
+        SLAndroidConfigurationItf config;
+        result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
+        PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
+        if(SL_RESULT_SUCCESS == result)
+        {
+            SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+            result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
+                sizeof(streamType));
+            PRINTERR(result, "config->SetConfiguration");
+        }
+
+        /* Clear any error since this was optional. */
+        result = SL_RESULT_SUCCESS;
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
+        PRINTERR(result, "bufferQueue->Realize");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+        mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
+    }
+
+    if(SL_RESULT_SUCCESS != result)
+    {
+        if(mBufferQueueObj)
+            VCALL0(mBufferQueueObj,Destroy)();
+        mBufferQueueObj = nullptr;
+
+        return false;
+    }
+
+    return true;
+}
+
+void OpenSLPlayback::start()
+{
+    mRing->reset();
+
+    SLAndroidSimpleBufferQueueItf bufferQueue;
+    SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+        &bufferQueue)};
+    PRINTERR(result, "bufferQueue->GetInterface");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
+        PRINTERR(result, "bufferQueue->RegisterCallback");
+    }
+    if(SL_RESULT_SUCCESS != result)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to register callback: 0x%08x", result};
+
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void OpenSLPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+
+    mSem.post();
+    mThread.join();
+
+    SLPlayItf player;
+    SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
+    PRINTERR(result, "bufferQueue->GetInterface");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
+        PRINTERR(result, "player->SetPlayState");
+    }
+
+    SLAndroidSimpleBufferQueueItf bufferQueue;
+    result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
+    PRINTERR(result, "bufferQueue->GetInterface");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL0(bufferQueue,Clear)();
+        PRINTERR(result, "bufferQueue->Clear");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
+        PRINTERR(result, "bufferQueue->RegisterCallback");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        SLAndroidSimpleBufferQueueState state;
+        do {
+            std::this_thread::yield();
+            result = VCALL(bufferQueue,GetState)(&state);
+        } while(SL_RESULT_SUCCESS == result && state.count > 0);
+        PRINTERR(result, "bufferQueue->GetState");
+    }
+}
+
+ClockLatency OpenSLPlayback::getClockLatency()
+{
+    ClockLatency ret;
+
+    std::lock_guard<std::mutex> _{mMutex};
+    ret.ClockTime = GetDeviceClockTime(mDevice);
+    ret.Latency  = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+
+struct OpenSLCapture final : public BackendBase {
+    OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~OpenSLCapture() override;
+
+    void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
+    static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
+    { static_cast<OpenSLCapture*>(context)->process(bq); }
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    /* engine interfaces */
+    SLObjectItf mEngineObj{nullptr};
+    SLEngineItf mEngine;
+
+    /* recording interfaces */
+    SLObjectItf mRecordObj{nullptr};
+
+    RingBufferPtr mRing{nullptr};
+    uint mSplOffset{0u};
+
+    uint mFrameSize{0};
+
+    DEF_NEWDEL(OpenSLCapture)
+};
+
+OpenSLCapture::~OpenSLCapture()
+{
+    if(mRecordObj)
+        VCALL0(mRecordObj,Destroy)();
+    mRecordObj = nullptr;
+
+    if(mEngineObj)
+        VCALL0(mEngineObj,Destroy)();
+    mEngineObj = nullptr;
+    mEngine = nullptr;
+}
+
+
+void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
+{
+    /* A new chunk has been written into the ring buffer, advance it. */
+    mRing->writeAdvance(1);
+}
+
+
+void OpenSLCapture::open(const char* name)
+{
+    if(!name)
+        name = opensl_device;
+    else if(strcmp(name, opensl_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
+    PRINTERR(result, "slCreateEngine");
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
+        PRINTERR(result, "engine->Realize");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
+        PRINTERR(result, "engine->GetInterface");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        mFrameSize = mDevice->frameSizeFromFmt();
+        /* Ensure the total length is at least 100ms */
+        uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
+        /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
+        uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
+            mDevice->Frequency/100*5)};
+        uint num_updates{(length+update_len-1) / update_len};
+
+        mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
+
+        mDevice->UpdateSize = update_len;
+        mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
+        const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
+
+        SLDataLocator_IODevice loc_dev{};
+        loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
+        loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
+        loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+        loc_dev.device = nullptr;
+
+        SLDataSource audioSrc{};
+        audioSrc.pLocator = &loc_dev;
+        audioSrc.pFormat = nullptr;
+
+        SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
+        loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+        loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
+
+        SLDataSink audioSnk{};
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+        SLAndroidDataFormat_PCM_EX format_pcm_ex{};
+        format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+        format_pcm_ex.numChannels = mDevice->channelsFromFmt();
+        format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
+        format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
+        format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
+        format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
+        format_pcm_ex.endianness = GetByteOrderEndianness();
+        format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
+
+        audioSnk.pLocator = &loc_bq;
+        audioSnk.pFormat = &format_pcm_ex;
+        result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
+            ids.size(), ids.data(), reqs.data());
+        if(SL_RESULT_SUCCESS != result)
+#endif
+        {
+            /* Fallback to SLDataFormat_PCM only if it supports the desired
+             * sample type.
+             */
+            if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort
+                || mDevice->FmtType == DevFmtInt)
+            {
+                SLDataFormat_PCM format_pcm{};
+                format_pcm.formatType = SL_DATAFORMAT_PCM;
+                format_pcm.numChannels = mDevice->channelsFromFmt();
+                format_pcm.samplesPerSec = mDevice->Frequency * 1000;
+                format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+                format_pcm.containerSize = format_pcm.bitsPerSample;
+                format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+                format_pcm.endianness = GetByteOrderEndianness();
+
+                audioSnk.pLocator = &loc_bq;
+                audioSnk.pFormat = &format_pcm;
+                result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
+                    ids.size(), ids.data(), reqs.data());
+            }
+            PRINTERR(result, "engine->CreateAudioRecorder");
+        }
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        /* Set the record preset to "generic", if possible. */
+        SLAndroidConfigurationItf config;
+        result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
+        PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
+        if(SL_RESULT_SUCCESS == result)
+        {
+            SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
+            result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
+                sizeof(preset));
+            PRINTERR(result, "config->SetConfiguration");
+        }
+
+        /* Clear any error since this was optional. */
+        result = SL_RESULT_SUCCESS;
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
+        PRINTERR(result, "recordObj->Realize");
+    }
+
+    SLAndroidSimpleBufferQueueItf bufferQueue;
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
+        PRINTERR(result, "recordObj->GetInterface");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
+        PRINTERR(result, "bufferQueue->RegisterCallback");
+    }
+    if(SL_RESULT_SUCCESS == result)
+    {
+        const uint chunk_size{mDevice->UpdateSize * mFrameSize};
+
+        auto data = mRing->getWriteVector();
+        for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
+        {
+            result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
+            PRINTERR(result, "bufferQueue->Enqueue");
+        }
+        for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
+        {
+            result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
+            PRINTERR(result, "bufferQueue->Enqueue");
+        }
+    }
+
+    if(SL_RESULT_SUCCESS != result)
+    {
+        if(mRecordObj)
+            VCALL0(mRecordObj,Destroy)();
+        mRecordObj = nullptr;
+
+        if(mEngineObj)
+            VCALL0(mEngineObj,Destroy)();
+        mEngineObj = nullptr;
+        mEngine = nullptr;
+
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to initialize OpenSL device: 0x%08x", result};
+    }
+
+    mDevice->DeviceName = name;
+}
+
+void OpenSLCapture::start()
+{
+    SLRecordItf record;
+    SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
+    PRINTERR(result, "recordObj->GetInterface");
+
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
+        PRINTERR(result, "record->SetRecordState");
+    }
+    if(SL_RESULT_SUCCESS != result)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start capture: 0x%08x", result};
+}
+
+void OpenSLCapture::stop()
+{
+    SLRecordItf record;
+    SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
+    PRINTERR(result, "recordObj->GetInterface");
+
+    if(SL_RESULT_SUCCESS == result)
+    {
+        result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
+        PRINTERR(result, "record->SetRecordState");
+    }
+}
+
+void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
+{
+    const uint update_size{mDevice->UpdateSize};
+    const uint chunk_size{update_size * mFrameSize};
+
+    /* Read the desired samples from the ring buffer then advance its read
+     * pointer.
+     */
+    size_t adv_count{0};
+    auto rdata = mRing->getReadVector();
+    for(uint i{0};i < samples;)
+    {
+        const uint rem{minu(samples - i, update_size - mSplOffset)};
+        std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
+            buffer + i*size_t{mFrameSize});
+
+        mSplOffset += rem;
+        if(mSplOffset == update_size)
+        {
+            /* Finished a chunk, reset the offset and advance the read pointer. */
+            mSplOffset = 0;
+
+            ++adv_count;
+            rdata.first.len -= 1;
+            if(!rdata.first.len)
+                rdata.first = rdata.second;
+            else
+                rdata.first.buf += chunk_size;
+        }
+
+        i += rem;
+    }
+    mRing->readAdvance(adv_count);
+
+    SLAndroidSimpleBufferQueueItf bufferQueue{};
+    if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
+    {
+        const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+            &bufferQueue)};
+        PRINTERR(result, "recordObj->GetInterface");
+        if UNLIKELY(SL_RESULT_SUCCESS != result)
+        {
+            mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
+            bufferQueue = nullptr;
+        }
+    }
+
+    if LIKELY(bufferQueue)
+    {
+        SLresult result{SL_RESULT_SUCCESS};
+        auto wdata = mRing->getWriteVector();
+        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++)
+        {
+            result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
+            PRINTERR(result, "bufferQueue->Enqueue");
+        }
+    }
+}
+
+uint OpenSLCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
+
+} // namespace
+
+bool OSLBackendFactory::init() { return true; }
+
+bool OSLBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string OSLBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+    case BackendType::Capture:
+        /* Includes null char. */
+        outnames.append(opensl_device, sizeof(opensl_device));
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new OpenSLPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new OpenSLCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &OSLBackendFactory::getFactory()
+{
+    static OSLBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/opensl.h

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

+ 0 - 878
Engine/lib/openal-soft/Alc/backends/oss.c

@@ -1,878 +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 <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <memory.h>
-#include <unistd.h>
-#include <errno.h>
-#include <math.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <sys/soundcard.h>
-
-/*
- * The OSS documentation talks about SOUND_MIXER_READ, but the header
- * only contains MIXER_READ. Play safe. Same for WRITE.
- */
-#ifndef SOUND_MIXER_READ
-#define SOUND_MIXER_READ MIXER_READ
-#endif
-#ifndef SOUND_MIXER_WRITE
-#define SOUND_MIXER_WRITE MIXER_WRITE
-#endif
-
-#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
-#define ALC_OSS_COMPAT
-#endif
-#ifndef SNDCTL_AUDIOINFO
-#define ALC_OSS_COMPAT
-#endif
-
-/*
- * FreeBSD strongly discourages the use of specific devices,
- * such as those returned in oss_audioinfo.devnode
- */
-#ifdef __FreeBSD__
-#define ALC_OSS_DEVNODE_TRUC
-#endif
-
-struct oss_device {
-    const ALCchar *handle;
-    const char *path;
-    struct oss_device *next;
-};
-
-static struct oss_device oss_playback = {
-    "OSS Default",
-    "/dev/dsp",
-    NULL
-};
-
-static struct oss_device oss_capture = {
-    "OSS Default",
-    "/dev/dsp",
-    NULL
-};
-
-#ifdef ALC_OSS_COMPAT
-
-#define DSP_CAP_OUTPUT 0x00020000
-#define DSP_CAP_INPUT 0x00010000
-static void ALCossListPopulate(struct oss_device *UNUSED(devlist), int UNUSED(type_flag))
-{
-}
-
-#else
-
-#ifndef HAVE_STRNLEN
-static size_t strnlen(const char *str, size_t maxlen)
-{
-    const char *end = memchr(str, 0, maxlen);
-    if(!end) return maxlen;
-    return end - str;
-}
-#endif
-
-static void ALCossListAppend(struct oss_device *list, const char *handle, size_t hlen, const char *path, size_t plen)
-{
-    struct oss_device *next;
-    struct oss_device *last;
-    size_t i;
-
-    /* skip the first item "OSS Default" */
-    last = list;
-    next = list->next;
-#ifdef ALC_OSS_DEVNODE_TRUC
-    for(i = 0;i < plen;i++)
-    {
-        if(path[i] == '.')
-        {
-            if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
-                hlen = hlen + i - plen;
-            plen = i;
-        }
-    }
-#else
-    (void)i;
-#endif
-    if(handle[0] == '\0')
-    {
-        handle = path;
-        hlen = plen;
-    }
-
-    while(next != NULL)
-    {
-        if(strncmp(next->path, path, plen) == 0)
-            return;
-        last = next;
-        next = next->next;
-    }
-
-    next = (struct oss_device*)malloc(sizeof(struct oss_device) + hlen + plen + 2);
-    next->handle = (char*)(next + 1);
-    next->path = next->handle + hlen + 1;
-    next->next = NULL;
-    last->next = next;
-
-    strncpy((char*)next->handle, handle, hlen);
-    ((char*)next->handle)[hlen] = '\0';
-    strncpy((char*)next->path, path, plen);
-    ((char*)next->path)[plen] = '\0';
-
-    TRACE("Got device \"%s\", \"%s\"\n", next->handle, next->path);
-}
-
-static void ALCossListPopulate(struct oss_device *devlist, int type_flag)
-{
-    struct oss_sysinfo si;
-    struct oss_audioinfo ai;
-    int fd, i;
-
-    if((fd=open("/dev/mixer", O_RDONLY)) < 0)
-    {
-        TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
-        return;
-    }
-    if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
-    {
-        TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
-        goto done;
-    }
-    for(i = 0;i < si.numaudios;i++)
-    {
-        const char *handle;
-        size_t len;
-
-        ai.dev = i;
-        if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
-        {
-            ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
-            continue;
-        }
-        if(ai.devnode[0] == '\0')
-            continue;
-
-        if(ai.handle[0] != '\0')
-        {
-            len = strnlen(ai.handle, sizeof(ai.handle));
-            handle = ai.handle;
-        }
-        else
-        {
-            len = strnlen(ai.name, sizeof(ai.name));
-            handle = ai.name;
-        }
-        if((ai.caps&type_flag))
-            ALCossListAppend(devlist, handle, len, ai.devnode,
-                             strnlen(ai.devnode, sizeof(ai.devnode)));
-    }
-
-done:
-    close(fd);
-}
-
-#endif
-
-static void ALCossListFree(struct oss_device *list)
-{
-    struct oss_device *cur;
-    if(list == NULL)
-        return;
-
-    /* skip the first item "OSS Default" */
-    cur = list->next;
-    list->next = NULL;
-
-    while(cur != NULL)
-    {
-        struct oss_device *next = cur->next;
-        free(cur);
-        cur = next;
-    }
-}
-
-static int log2i(ALCuint x)
-{
-    int y = 0;
-    while (x > 1)
-    {
-        x >>= 1;
-        y++;
-    }
-    return y;
-}
-
-typedef struct ALCplaybackOSS {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    int fd;
-
-    ALubyte *mix_data;
-    int data_size;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCplaybackOSS;
-
-static int ALCplaybackOSS_mixerProc(void *ptr);
-
-static void ALCplaybackOSS_Construct(ALCplaybackOSS *self, ALCdevice *device);
-static void ALCplaybackOSS_Destruct(ALCplaybackOSS *self);
-static ALCenum ALCplaybackOSS_open(ALCplaybackOSS *self, const ALCchar *name);
-static ALCboolean ALCplaybackOSS_reset(ALCplaybackOSS *self);
-static ALCboolean ALCplaybackOSS_start(ALCplaybackOSS *self);
-static void ALCplaybackOSS_stop(ALCplaybackOSS *self);
-static DECLARE_FORWARD2(ALCplaybackOSS, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
-static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCplaybackOSS, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCplaybackOSS)
-DEFINE_ALCBACKEND_VTABLE(ALCplaybackOSS);
-
-
-static int ALCplaybackOSS_mixerProc(void *ptr)
-{
-    ALCplaybackOSS *self = (ALCplaybackOSS*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct timeval timeout;
-    ALubyte *write_ptr;
-    ALint frame_size;
-    ALint to_write;
-    ssize_t wrote;
-    fd_set wfds;
-    int sret;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    ALCplaybackOSS_lock(self);
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        FD_ZERO(&wfds);
-        FD_SET(self->fd, &wfds);
-        timeout.tv_sec = 1;
-        timeout.tv_usec = 0;
-
-        ALCplaybackOSS_unlock(self);
-        sret = select(self->fd+1, NULL, &wfds, NULL, &timeout);
-        ALCplaybackOSS_lock(self);
-        if(sret < 0)
-        {
-            if(errno == EINTR)
-                continue;
-            ERR("select failed: %s\n", strerror(errno));
-            aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
-            break;
-        }
-        else if(sret == 0)
-        {
-            WARN("select timeout\n");
-            continue;
-        }
-
-        write_ptr = self->mix_data;
-        to_write = self->data_size;
-        aluMixData(device, write_ptr, to_write/frame_size);
-        while(to_write > 0 && !ATOMIC_LOAD_SEQ(&self->killNow))
-        {
-            wrote = write(self->fd, write_ptr, to_write);
-            if(wrote < 0)
-            {
-                if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-                    continue;
-                ERR("write failed: %s\n", strerror(errno));
-                aluHandleDisconnect(device, "Failed writing playback samples: %s",
-                                    strerror(errno));
-                break;
-            }
-
-            to_write -= wrote;
-            write_ptr += wrote;
-        }
-    }
-    ALCplaybackOSS_unlock(self);
-
-    return 0;
-}
-
-
-static void ALCplaybackOSS_Construct(ALCplaybackOSS *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCplaybackOSS, ALCbackend, self);
-
-    self->fd = -1;
-    ATOMIC_INIT(&self->killNow, AL_FALSE);
-}
-
-static void ALCplaybackOSS_Destruct(ALCplaybackOSS *self)
-{
-    if(self->fd != -1)
-        close(self->fd);
-    self->fd = -1;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static ALCenum ALCplaybackOSS_open(ALCplaybackOSS *self, const ALCchar *name)
-{
-    struct oss_device *dev = &oss_playback;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    if(!name || strcmp(name, dev->handle) == 0)
-        name = dev->handle;
-    else
-    {
-        if(!dev->next)
-        {
-            ALCossListPopulate(&oss_playback, DSP_CAP_OUTPUT);
-            dev = &oss_playback;
-        }
-        while(dev != NULL)
-        {
-            if (strcmp(dev->handle, name) == 0)
-                break;
-            dev = dev->next;
-        }
-        if(dev == NULL)
-        {
-            WARN("Could not find \"%s\" in device list\n", name);
-            return ALC_INVALID_VALUE;
-        }
-    }
-
-    self->fd = open(dev->path, O_WRONLY);
-    if(self->fd == -1)
-    {
-        ERR("Could not open %s: %s\n", dev->path, strerror(errno));
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCplaybackOSS_reset(ALCplaybackOSS *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    int numFragmentsLogSize;
-    int log2FragmentSize;
-    unsigned int periods;
-    audio_buf_info info;
-    ALuint frameSize;
-    int numChannels;
-    int ossFormat;
-    int ossSpeed;
-    char *err;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            ossFormat = AFMT_S8;
-            break;
-        case DevFmtUByte:
-            ossFormat = AFMT_U8;
-            break;
-        case DevFmtUShort:
-        case DevFmtInt:
-        case DevFmtUInt:
-        case DevFmtFloat:
-            device->FmtType = DevFmtShort;
-            /* fall-through */
-        case DevFmtShort:
-            ossFormat = AFMT_S16_NE;
-            break;
-    }
-
-    periods = device->NumUpdates;
-    numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    ossSpeed = device->Frequency;
-    frameSize = numChannels * BytesFromDevFmt(device->FmtType);
-    /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
-    log2FragmentSize = maxi(log2i(device->UpdateSize*frameSize), 4);
-    numFragmentsLogSize = (periods << 16) | log2FragmentSize;
-
-#define CHECKERR(func) if((func) < 0) {                                       \
-    err = #func;                                                              \
-    goto err;                                                                 \
-}
-    /* Don't fail if SETFRAGMENT fails. We can handle just about anything
-     * that's reported back via GETOSPACE */
-    ioctl(self->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFMT, &ossFormat));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_CHANNELS, &numChannels));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_SPEED, &ossSpeed));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_GETOSPACE, &info));
-    if(0)
-    {
-    err:
-        ERR("%s failed: %s\n", err, strerror(errno));
-        return ALC_FALSE;
-    }
-#undef CHECKERR
-
-    if((int)ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != numChannels)
-    {
-        ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels);
-        return ALC_FALSE;
-    }
-
-    if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) ||
-         (ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) ||
-         (ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort)))
-    {
-        ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat);
-        return ALC_FALSE;
-    }
-
-    device->Frequency = ossSpeed;
-    device->UpdateSize = info.fragsize / frameSize;
-    device->NumUpdates = info.fragments;
-
-    SetDefaultChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCplaybackOSS_start(ALCplaybackOSS *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
-        device->FmtChans, device->FmtType, device->AmbiOrder
-    );
-    self->mix_data = calloc(1, self->data_size);
-
-    ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
-    if(althrd_create(&self->thread, ALCplaybackOSS_mixerProc, self) != althrd_success)
-    {
-        free(self->mix_data);
-        self->mix_data = NULL;
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCplaybackOSS_stop(ALCplaybackOSS *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
-        return;
-    althrd_join(self->thread, &res);
-
-    if(ioctl(self->fd, SNDCTL_DSP_RESET) != 0)
-        ERR("Error resetting device: %s\n", strerror(errno));
-
-    free(self->mix_data);
-    self->mix_data = NULL;
-}
-
-
-typedef struct ALCcaptureOSS {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    int fd;
-
-    ll_ringbuffer_t *ring;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCcaptureOSS;
-
-static int ALCcaptureOSS_recordProc(void *ptr);
-
-static void ALCcaptureOSS_Construct(ALCcaptureOSS *self, ALCdevice *device);
-static void ALCcaptureOSS_Destruct(ALCcaptureOSS *self);
-static ALCenum ALCcaptureOSS_open(ALCcaptureOSS *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCcaptureOSS_start(ALCcaptureOSS *self);
-static void ALCcaptureOSS_stop(ALCcaptureOSS *self);
-static ALCenum ALCcaptureOSS_captureSamples(ALCcaptureOSS *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCcaptureOSS_availableSamples(ALCcaptureOSS *self);
-static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCcaptureOSS, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCcaptureOSS)
-DEFINE_ALCBACKEND_VTABLE(ALCcaptureOSS);
-
-
-static int ALCcaptureOSS_recordProc(void *ptr)
-{
-    ALCcaptureOSS *self = (ALCcaptureOSS*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct timeval timeout;
-    int frame_size;
-    fd_set rfds;
-    ssize_t amt;
-    int sret;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), RECORD_THREAD_NAME);
-
-    frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    while(!ATOMIC_LOAD_SEQ(&self->killNow))
-    {
-        ll_ringbuffer_data_t vec[2];
-
-        FD_ZERO(&rfds);
-        FD_SET(self->fd, &rfds);
-        timeout.tv_sec = 1;
-        timeout.tv_usec = 0;
-
-        sret = select(self->fd+1, &rfds, NULL, NULL, &timeout);
-        if(sret < 0)
-        {
-            if(errno == EINTR)
-                continue;
-            ERR("select failed: %s\n", strerror(errno));
-            aluHandleDisconnect(device, "Failed to check capture samples: %s", strerror(errno));
-            break;
-        }
-        else if(sret == 0)
-        {
-            WARN("select timeout\n");
-            continue;
-        }
-
-        ll_ringbuffer_get_write_vector(self->ring, vec);
-        if(vec[0].len > 0)
-        {
-            amt = read(self->fd, vec[0].buf, vec[0].len*frame_size);
-            if(amt < 0)
-            {
-                ERR("read failed: %s\n", strerror(errno));
-                ALCcaptureOSS_lock(self);
-                aluHandleDisconnect(device, "Failed reading capture samples: %s", strerror(errno));
-                ALCcaptureOSS_unlock(self);
-                break;
-            }
-            ll_ringbuffer_write_advance(self->ring, amt/frame_size);
-        }
-    }
-
-    return 0;
-}
-
-
-static void ALCcaptureOSS_Construct(ALCcaptureOSS *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCcaptureOSS, ALCbackend, self);
-
-    self->fd = -1;
-    self->ring = NULL;
-    ATOMIC_INIT(&self->killNow, AL_FALSE);
-}
-
-static void ALCcaptureOSS_Destruct(ALCcaptureOSS *self)
-{
-    if(self->fd != -1)
-        close(self->fd);
-    self->fd = -1;
-
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static ALCenum ALCcaptureOSS_open(ALCcaptureOSS *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct oss_device *dev = &oss_capture;
-    int numFragmentsLogSize;
-    int log2FragmentSize;
-    unsigned int periods;
-    audio_buf_info info;
-    ALuint frameSize;
-    int numChannels;
-    int ossFormat;
-    int ossSpeed;
-    char *err;
-
-    if(!name || strcmp(name, dev->handle) == 0)
-        name = dev->handle;
-    else
-    {
-        if(!dev->next)
-        {
-            ALCossListPopulate(&oss_capture, DSP_CAP_INPUT);
-            dev = &oss_capture;
-        }
-        while(dev != NULL)
-        {
-            if (strcmp(dev->handle, name) == 0)
-                break;
-            dev = dev->next;
-        }
-        if(dev == NULL)
-        {
-            WARN("Could not find \"%s\" in device list\n", name);
-            return ALC_INVALID_VALUE;
-        }
-    }
-
-    self->fd = open(dev->path, O_RDONLY);
-    if(self->fd == -1)
-    {
-        ERR("Could not open %s: %s\n", dev->path, strerror(errno));
-        return ALC_INVALID_VALUE;
-    }
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            ossFormat = AFMT_S8;
-            break;
-        case DevFmtUByte:
-            ossFormat = AFMT_U8;
-            break;
-        case DevFmtShort:
-            ossFormat = AFMT_S16_NE;
-            break;
-        case DevFmtUShort:
-        case DevFmtInt:
-        case DevFmtUInt:
-        case DevFmtFloat:
-            ERR("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
-            return ALC_INVALID_VALUE;
-    }
-
-    periods = 4;
-    numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    frameSize = numChannels * BytesFromDevFmt(device->FmtType);
-    ossSpeed = device->Frequency;
-    log2FragmentSize = log2i(device->UpdateSize * device->NumUpdates *
-                             frameSize / periods);
-
-    /* according to the OSS spec, 16 bytes are the minimum */
-    if (log2FragmentSize < 4)
-        log2FragmentSize = 4;
-    numFragmentsLogSize = (periods << 16) | log2FragmentSize;
-
-#define CHECKERR(func) if((func) < 0) {                                       \
-    err = #func;                                                              \
-    goto err;                                                                 \
-}
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_SETFMT, &ossFormat));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_CHANNELS, &numChannels));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_SPEED, &ossSpeed));
-    CHECKERR(ioctl(self->fd, SNDCTL_DSP_GETISPACE, &info));
-    if(0)
-    {
-    err:
-        ERR("%s failed: %s\n", err, strerror(errno));
-        close(self->fd);
-        self->fd = -1;
-        return ALC_INVALID_VALUE;
-    }
-#undef CHECKERR
-
-    if((int)ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != numChannels)
-    {
-        ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels);
-        close(self->fd);
-        self->fd = -1;
-        return ALC_INVALID_VALUE;
-    }
-
-    if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) ||
-         (ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) ||
-         (ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort)))
-    {
-        ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat);
-        close(self->fd);
-        self->fd = -1;
-        return ALC_INVALID_VALUE;
-    }
-
-    self->ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates, frameSize, false);
-    if(!self->ring)
-    {
-        ERR("Ring buffer create failed\n");
-        close(self->fd);
-        self->fd = -1;
-        return ALC_OUT_OF_MEMORY;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCcaptureOSS_start(ALCcaptureOSS *self)
-{
-    ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
-    if(althrd_create(&self->thread, ALCcaptureOSS_recordProc, self) != althrd_success)
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCcaptureOSS_stop(ALCcaptureOSS *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
-        return;
-
-    althrd_join(self->thread, &res);
-
-    if(ioctl(self->fd, SNDCTL_DSP_RESET) != 0)
-        ERR("Error resetting device: %s\n", strerror(errno));
-}
-
-static ALCenum ALCcaptureOSS_captureSamples(ALCcaptureOSS *self, ALCvoid *buffer, ALCuint samples)
-{
-    ll_ringbuffer_read(self->ring, buffer, samples);
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCcaptureOSS_availableSamples(ALCcaptureOSS *self)
-{
-    return ll_ringbuffer_read_space(self->ring);
-}
-
-
-typedef struct ALCossBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCossBackendFactory;
-#define ALCOSSBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCossBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCossBackendFactory_getFactory(void);
-
-static ALCboolean ALCossBackendFactory_init(ALCossBackendFactory *self);
-static void ALCossBackendFactory_deinit(ALCossBackendFactory *self);
-static ALCboolean ALCossBackendFactory_querySupport(ALCossBackendFactory *self, ALCbackend_Type type);
-static void ALCossBackendFactory_probe(ALCossBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCossBackendFactory_createBackend(ALCossBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCossBackendFactory);
-
-
-ALCbackendFactory *ALCossBackendFactory_getFactory(void)
-{
-    static ALCossBackendFactory factory = ALCOSSBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-ALCboolean ALCossBackendFactory_init(ALCossBackendFactory* UNUSED(self))
-{
-    ConfigValueStr(NULL, "oss", "device", &oss_playback.path);
-    ConfigValueStr(NULL, "oss", "capture", &oss_capture.path);
-
-    return ALC_TRUE;
-}
-
-void  ALCossBackendFactory_deinit(ALCossBackendFactory* UNUSED(self))
-{
-    ALCossListFree(&oss_playback);
-    ALCossListFree(&oss_capture);
-}
-
-
-ALCboolean ALCossBackendFactory_querySupport(ALCossBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-void ALCossBackendFactory_probe(ALCossBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    struct oss_device *cur;
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            ALCossListFree(&oss_playback);
-            ALCossListPopulate(&oss_playback, DSP_CAP_OUTPUT);
-            cur = &oss_playback;
-            while(cur != NULL)
-            {
-#ifdef HAVE_STAT
-                struct stat buf;
-                if(stat(cur->path, &buf) == 0)
-#endif
-                    AppendAllDevicesList(cur->handle);
-                cur = cur->next;
-            }
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            ALCossListFree(&oss_capture);
-            ALCossListPopulate(&oss_capture, DSP_CAP_INPUT);
-            cur = &oss_capture;
-            while(cur != NULL)
-            {
-#ifdef HAVE_STAT
-                struct stat buf;
-                if(stat(cur->path, &buf) == 0)
-#endif
-                    AppendCaptureDeviceList(cur->handle);
-                cur = cur->next;
-            }
-            break;
-    }
-}
-
-ALCbackend* ALCossBackendFactory_createBackend(ALCossBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCplaybackOSS *backend;
-        NEW_OBJ(backend, ALCplaybackOSS)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCcaptureOSS *backend;
-        NEW_OBJ(backend, ALCcaptureOSS)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 686 - 0
Engine/lib/openal-soft/Alc/backends/oss.cpp

@@ -0,0 +1,686 @@
+/**
+ * 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 "backends/oss.h"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <atomic>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <new>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "alcmain.h"
+#include "alconfig.h"
+#include "albyte.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alu.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "vector.h"
+
+#include <sys/soundcard.h>
+
+/*
+ * The OSS documentation talks about SOUND_MIXER_READ, but the header
+ * only contains MIXER_READ. Play safe. Same for WRITE.
+ */
+#ifndef SOUND_MIXER_READ
+#define SOUND_MIXER_READ MIXER_READ
+#endif
+#ifndef SOUND_MIXER_WRITE
+#define SOUND_MIXER_WRITE MIXER_WRITE
+#endif
+
+#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
+#define ALC_OSS_COMPAT
+#endif
+#ifndef SNDCTL_AUDIOINFO
+#define ALC_OSS_COMPAT
+#endif
+
+/*
+ * FreeBSD strongly discourages the use of specific devices,
+ * such as those returned in oss_audioinfo.devnode
+ */
+#ifdef __FreeBSD__
+#define ALC_OSS_DEVNODE_TRUC
+#endif
+
+namespace {
+
+constexpr char DefaultName[] = "OSS Default";
+std::string DefaultPlayback{"/dev/dsp"};
+std::string DefaultCapture{"/dev/dsp"};
+
+struct DevMap {
+    std::string name;
+    std::string device_name;
+};
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+#ifdef ALC_OSS_COMPAT
+
+#define DSP_CAP_OUTPUT 0x00020000
+#define DSP_CAP_INPUT 0x00010000
+void ALCossListPopulate(al::vector<DevMap> &devlist, int type)
+{
+    devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
+}
+
+#else
+
+void ALCossListAppend(al::vector<DevMap> &list, al::span<const char> handle, al::span<const char> path)
+{
+#ifdef ALC_OSS_DEVNODE_TRUC
+    for(size_t i{0};i < path.size();++i)
+    {
+        if(path[i] == '.' && handle.size() + i >= path.size())
+        {
+            const size_t hoffset{handle.size() + i - path.size()};
+            if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
+                handle = handle.first(hoffset);
+            path = path.first(i);
+        }
+    }
+#endif
+    if(handle.empty())
+        handle = path;
+
+    std::string basename{handle.data(), handle.size()};
+    std::string devname{path.data(), path.size()};
+
+    auto match_devname = [&devname](const DevMap &entry) -> bool
+    { return entry.device_name == devname; };
+    if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
+        return;
+
+    auto checkName = [&list](const std::string &name) -> bool
+    {
+        auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
+        return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
+    };
+    int count{1};
+    std::string newname{basename};
+    while(checkName(newname))
+    {
+        newname = basename;
+        newname += " #";
+        newname += std::to_string(++count);
+    }
+
+    list.emplace_back(DevMap{std::move(newname), std::move(devname)});
+    const DevMap &entry = list.back();
+
+    TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
+}
+
+void ALCossListPopulate(al::vector<DevMap> &devlist, int type_flag)
+{
+    int fd{open("/dev/mixer", O_RDONLY)};
+    if(fd < 0)
+    {
+        TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
+        goto done;
+    }
+
+    oss_sysinfo si;
+    if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
+    {
+        TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
+        goto done;
+    }
+
+    for(int i{0};i < si.numaudios;i++)
+    {
+        oss_audioinfo ai;
+        ai.dev = i;
+        if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
+        {
+            ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
+            continue;
+        }
+        if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
+            continue;
+
+        al::span<const char> handle;
+        if(ai.handle[0] != '\0')
+            handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
+        else
+            handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
+        al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
+
+        ALCossListAppend(devlist, handle, devnode);
+    }
+
+done:
+    if(fd >= 0)
+        close(fd);
+    fd = -1;
+
+    const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
+    auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
+        [defdev](const DevMap &entry) -> bool
+        { return entry.device_name == defdev; }
+    );
+    if(iter == devlist.cend())
+        devlist.insert(devlist.begin(), DevMap{DefaultName, defdev});
+    else
+    {
+        DevMap entry{std::move(*iter)};
+        devlist.erase(iter);
+        devlist.insert(devlist.begin(), std::move(entry));
+    }
+    devlist.shrink_to_fit();
+}
+
+#endif
+
+uint log2i(uint x)
+{
+    uint y{0};
+    while(x > 1)
+    {
+        x >>= 1;
+        y++;
+    }
+    return y;
+}
+
+
+struct OSSPlayback final : public BackendBase {
+    OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~OSSPlayback() override;
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    int mFd{-1};
+
+    al::vector<al::byte> mMixData;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(OSSPlayback)
+};
+
+OSSPlayback::~OSSPlayback()
+{
+    if(mFd != -1)
+        close(mFd);
+    mFd = -1;
+}
+
+
+int OSSPlayback::mixerProc()
+{
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    const size_t frame_step{mDevice->channelsFromFmt()};
+    const size_t frame_size{mDevice->frameSizeFromFmt()};
+
+    while(!mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
+    {
+        pollfd pollitem{};
+        pollitem.fd = mFd;
+        pollitem.events = POLLOUT;
+
+        int pret{poll(&pollitem, 1, 1000)};
+        if(pret < 0)
+        {
+            if(errno == EINTR || errno == EAGAIN)
+                continue;
+            ERR("poll failed: %s\n", strerror(errno));
+            mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
+            break;
+        }
+        else if(pret == 0)
+        {
+            WARN("poll timeout\n");
+            continue;
+        }
+
+        al::byte *write_ptr{mMixData.data()};
+        size_t to_write{mMixData.size()};
+        mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
+        while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
+        {
+            ssize_t wrote{write(mFd, write_ptr, to_write)};
+            if(wrote < 0)
+            {
+                if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+                    continue;
+                ERR("write failed: %s\n", strerror(errno));
+                mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
+                break;
+            }
+
+            to_write -= static_cast<size_t>(wrote);
+            write_ptr += wrote;
+        }
+    }
+
+    return 0;
+}
+
+
+void OSSPlayback::open(const char *name)
+{
+    const char *devname{DefaultPlayback.c_str()};
+    if(!name)
+        name = DefaultName;
+    else
+    {
+        if(PlaybackDevices.empty())
+            ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
+
+        auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+            [&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};
+        devname = iter->device_name.c_str();
+    }
+
+    mFd = ::open(devname, O_WRONLY);
+    if(mFd == -1)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
+            strerror(errno)};
+
+    mDevice->DeviceName = name;
+}
+
+bool OSSPlayback::reset()
+{
+    int ossFormat{};
+    switch(mDevice->FmtType)
+    {
+        case DevFmtByte:
+            ossFormat = AFMT_S8;
+            break;
+        case DevFmtUByte:
+            ossFormat = AFMT_U8;
+            break;
+        case DevFmtUShort:
+        case DevFmtInt:
+        case DevFmtUInt:
+        case DevFmtFloat:
+            mDevice->FmtType = DevFmtShort;
+            /* fall-through */
+        case DevFmtShort:
+            ossFormat = AFMT_S16_NE;
+            break;
+    }
+
+    uint periods{mDevice->BufferSize / mDevice->UpdateSize};
+    uint numChannels{mDevice->channelsFromFmt()};
+    uint ossSpeed{mDevice->Frequency};
+    uint frameSize{numChannels * mDevice->bytesFromFmt()};
+    /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
+    uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
+    uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
+
+    audio_buf_info info{};
+    const char *err;
+#define CHECKERR(func) if((func) < 0) {                                       \
+    err = #func;                                                              \
+    goto err;                                                                 \
+}
+    /* Don't fail if SETFRAGMENT fails. We can handle just about anything
+     * that's reported back via GETOSPACE */
+    ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
+    if(0)
+    {
+    err:
+        ERR("%s failed: %s\n", err, strerror(errno));
+        return false;
+    }
+#undef CHECKERR
+
+    if(mDevice->channelsFromFmt() != numChannels)
+    {
+        ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
+            numChannels);
+        return false;
+    }
+
+    if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
+         (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
+         (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
+    {
+        ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
+            ossFormat);
+        return false;
+    }
+
+    mDevice->Frequency = ossSpeed;
+    mDevice->UpdateSize = static_cast<uint>(info.fragsize) / frameSize;
+    mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
+
+    setDefaultChannelOrder();
+
+    mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+
+    return true;
+}
+
+void OSSPlayback::start()
+{
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void OSSPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
+        ERR("Error resetting device: %s\n", strerror(errno));
+}
+
+
+struct OSScapture final : public BackendBase {
+    OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~OSScapture() override;
+
+    int recordProc();
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    int mFd{-1};
+
+    RingBufferPtr mRing{nullptr};
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(OSScapture)
+};
+
+OSScapture::~OSScapture()
+{
+    if(mFd != -1)
+        close(mFd);
+    mFd = -1;
+}
+
+
+int OSScapture::recordProc()
+{
+    SetRTPriority();
+    althrd_setname(RECORD_THREAD_NAME);
+
+    const size_t frame_size{mDevice->frameSizeFromFmt()};
+    while(!mKillNow.load(std::memory_order_acquire))
+    {
+        pollfd pollitem{};
+        pollitem.fd = mFd;
+        pollitem.events = POLLIN;
+
+        int sret{poll(&pollitem, 1, 1000)};
+        if(sret < 0)
+        {
+            if(errno == EINTR || errno == EAGAIN)
+                continue;
+            ERR("poll failed: %s\n", strerror(errno));
+            mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno));
+            break;
+        }
+        else if(sret == 0)
+        {
+            WARN("poll timeout\n");
+            continue;
+        }
+
+        auto vec = mRing->getWriteVector();
+        if(vec.first.len > 0)
+        {
+            ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
+            if(amt < 0)
+            {
+                ERR("read failed: %s\n", strerror(errno));
+                mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
+                break;
+            }
+            mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
+        }
+    }
+
+    return 0;
+}
+
+
+void OSScapture::open(const char *name)
+{
+    const char *devname{DefaultCapture.c_str()};
+    if(!name)
+        name = DefaultName;
+    else
+    {
+        if(CaptureDevices.empty())
+            ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
+
+        auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+            [&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};
+        devname = iter->device_name.c_str();
+    }
+
+    mFd = ::open(devname, O_RDONLY);
+    if(mFd == -1)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
+            strerror(errno)};
+
+    int ossFormat{};
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        ossFormat = AFMT_S8;
+        break;
+    case DevFmtUByte:
+        ossFormat = AFMT_U8;
+        break;
+    case DevFmtShort:
+        ossFormat = AFMT_S16_NE;
+        break;
+    case DevFmtUShort:
+    case DevFmtInt:
+    case DevFmtUInt:
+    case DevFmtFloat:
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
+    }
+
+    uint periods{4};
+    uint numChannels{mDevice->channelsFromFmt()};
+    uint frameSize{numChannels * mDevice->bytesFromFmt()};
+    uint ossSpeed{mDevice->Frequency};
+    /* according to the OSS spec, 16 bytes are the minimum */
+    uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
+    uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
+
+    audio_buf_info info{};
+#define CHECKERR(func) if((func) < 0) {                                       \
+    throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \
+        strerror(errno)};                                                     \
+}
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
+    CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
+#undef CHECKERR
+
+    if(mDevice->channelsFromFmt() != numChannels)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
+            numChannels};
+
+    if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
+        || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
+        || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
+            ossFormat};
+
+    mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
+
+    mDevice->DeviceName = name;
+}
+
+void OSScapture::start()
+{
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start recording thread: %s", e.what()};
+    }
+}
+
+void OSScapture::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
+        ERR("Error resetting device: %s\n", strerror(errno));
+}
+
+void OSScapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+uint OSScapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+} // namespace
+
+
+BackendFactory &OSSBackendFactory::getFactory()
+{
+    static OSSBackendFactory factory{};
+    return factory;
+}
+
+bool OSSBackendFactory::init()
+{
+    if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
+        DefaultPlayback = std::move(*devopt);
+    if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
+        DefaultCapture = std::move(*capopt);
+
+    return true;
+}
+
+bool OSSBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string OSSBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+
+    auto add_device = [&outnames](const DevMap &entry) -> void
+    {
+        struct stat buf;
+        if(stat(entry.device_name.c_str(), &buf) == 0)
+        {
+            /* Includes null char. */
+            outnames.append(entry.name.c_str(), entry.name.length()+1);
+        }
+    };
+
+    switch(type)
+    {
+    case BackendType::Playback:
+        PlaybackDevices.clear();
+        ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
+        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        break;
+
+    case BackendType::Capture:
+        CaptureDevices.clear();
+        ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+
+    return outnames;
+}
+
+BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new OSSPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new OSScapture{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/oss.h

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

+ 0 - 558
Engine/lib/openal-soft/Alc/backends/portaudio.c

@@ -1,558 +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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <portaudio.h>
-
-
-static const ALCchar pa_device[] = "PortAudio Default";
-
-
-#ifdef HAVE_DYNLOAD
-static void *pa_handle;
-#define MAKE_FUNC(x) static __typeof(x) * p##x
-MAKE_FUNC(Pa_Initialize);
-MAKE_FUNC(Pa_Terminate);
-MAKE_FUNC(Pa_GetErrorText);
-MAKE_FUNC(Pa_StartStream);
-MAKE_FUNC(Pa_StopStream);
-MAKE_FUNC(Pa_OpenStream);
-MAKE_FUNC(Pa_CloseStream);
-MAKE_FUNC(Pa_GetDefaultOutputDevice);
-MAKE_FUNC(Pa_GetDefaultInputDevice);
-MAKE_FUNC(Pa_GetStreamInfo);
-#undef MAKE_FUNC
-
-#define Pa_Initialize                  pPa_Initialize
-#define Pa_Terminate                   pPa_Terminate
-#define Pa_GetErrorText                pPa_GetErrorText
-#define Pa_StartStream                 pPa_StartStream
-#define Pa_StopStream                  pPa_StopStream
-#define Pa_OpenStream                  pPa_OpenStream
-#define Pa_CloseStream                 pPa_CloseStream
-#define Pa_GetDefaultOutputDevice      pPa_GetDefaultOutputDevice
-#define Pa_GetDefaultInputDevice       pPa_GetDefaultInputDevice
-#define Pa_GetStreamInfo               pPa_GetStreamInfo
-#endif
-
-static ALCboolean pa_load(void)
-{
-    PaError err;
-
-#ifdef HAVE_DYNLOAD
-    if(!pa_handle)
-    {
-#ifdef _WIN32
-# define PALIB "portaudio.dll"
-#elif defined(__APPLE__) && defined(__MACH__)
-# define PALIB "libportaudio.2.dylib"
-#elif defined(__OpenBSD__)
-# define PALIB "libportaudio.so"
-#else
-# define PALIB "libportaudio.so.2"
-#endif
-
-        pa_handle = LoadLib(PALIB);
-        if(!pa_handle)
-            return ALC_FALSE;
-
-#define LOAD_FUNC(f) do {                                                     \
-    p##f = GetSymbol(pa_handle, #f);                                          \
-    if(p##f == NULL)                                                          \
-    {                                                                         \
-        CloseLib(pa_handle);                                                  \
-        pa_handle = NULL;                                                     \
-        return ALC_FALSE;                                                     \
-    }                                                                         \
-} while(0)
-        LOAD_FUNC(Pa_Initialize);
-        LOAD_FUNC(Pa_Terminate);
-        LOAD_FUNC(Pa_GetErrorText);
-        LOAD_FUNC(Pa_StartStream);
-        LOAD_FUNC(Pa_StopStream);
-        LOAD_FUNC(Pa_OpenStream);
-        LOAD_FUNC(Pa_CloseStream);
-        LOAD_FUNC(Pa_GetDefaultOutputDevice);
-        LOAD_FUNC(Pa_GetDefaultInputDevice);
-        LOAD_FUNC(Pa_GetStreamInfo);
-#undef LOAD_FUNC
-
-        if((err=Pa_Initialize()) != paNoError)
-        {
-            ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
-            CloseLib(pa_handle);
-            pa_handle = NULL;
-            return ALC_FALSE;
-        }
-    }
-#else
-    if((err=Pa_Initialize()) != paNoError)
-    {
-        ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
-        return ALC_FALSE;
-    }
-#endif
-    return ALC_TRUE;
-}
-
-
-typedef struct ALCportPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    PaStream *stream;
-    PaStreamParameters params;
-    ALuint update_size;
-} ALCportPlayback;
-
-static int ALCportPlayback_WriteCallback(const void *inputBuffer, void *outputBuffer,
-    unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
-    const PaStreamCallbackFlags statusFlags, void *userData);
-
-static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device);
-static void ALCportPlayback_Destruct(ALCportPlayback *self);
-static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name);
-static ALCboolean ALCportPlayback_reset(ALCportPlayback *self);
-static ALCboolean ALCportPlayback_start(ALCportPlayback *self);
-static void ALCportPlayback_stop(ALCportPlayback *self);
-static DECLARE_FORWARD2(ALCportPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
-static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCportPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCportPlayback);
-
-
-static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCportPlayback, ALCbackend, self);
-
-    self->stream = NULL;
-}
-
-static void ALCportPlayback_Destruct(ALCportPlayback *self)
-{
-    PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError;
-    if(err != paNoError)
-        ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
-    self->stream = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCportPlayback_WriteCallback(const void *UNUSED(inputBuffer), void *outputBuffer,
-    unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo),
-    const PaStreamCallbackFlags UNUSED(statusFlags), void *userData)
-{
-    ALCportPlayback *self = userData;
-
-    ALCportPlayback_lock(self);
-    aluMixData(STATIC_CAST(ALCbackend, self)->mDevice, outputBuffer, framesPerBuffer);
-    ALCportPlayback_unlock(self);
-    return 0;
-}
-
-
-static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    PaError err;
-
-    if(!name)
-        name = pa_device;
-    else if(strcmp(name, pa_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    self->update_size = device->UpdateSize;
-
-    self->params.device = -1;
-    if(!ConfigValueInt(NULL, "port", "device", &self->params.device) ||
-       self->params.device < 0)
-        self->params.device = Pa_GetDefaultOutputDevice();
-    self->params.suggestedLatency = (device->UpdateSize*device->NumUpdates) /
-                                    (float)device->Frequency;
-    self->params.hostApiSpecificStreamInfo = NULL;
-
-    self->params.channelCount = ((device->FmtChans == DevFmtMono) ? 1 : 2);
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            self->params.sampleFormat = paInt8;
-            break;
-        case DevFmtUByte:
-            self->params.sampleFormat = paUInt8;
-            break;
-        case DevFmtUShort:
-            /* fall-through */
-        case DevFmtShort:
-            self->params.sampleFormat = paInt16;
-            break;
-        case DevFmtUInt:
-            /* fall-through */
-        case DevFmtInt:
-            self->params.sampleFormat = paInt32;
-            break;
-        case DevFmtFloat:
-            self->params.sampleFormat = paFloat32;
-            break;
-    }
-
-retry_open:
-    err = Pa_OpenStream(&self->stream, NULL, &self->params,
-        device->Frequency, device->UpdateSize, paNoFlag,
-        ALCportPlayback_WriteCallback, self
-    );
-    if(err != paNoError)
-    {
-        if(self->params.sampleFormat == paFloat32)
-        {
-            self->params.sampleFormat = paInt16;
-            goto retry_open;
-        }
-        ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-
-}
-
-static ALCboolean ALCportPlayback_reset(ALCportPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const PaStreamInfo *streamInfo;
-
-    streamInfo = Pa_GetStreamInfo(self->stream);
-    device->Frequency = streamInfo->sampleRate;
-    device->UpdateSize = self->update_size;
-
-    if(self->params.sampleFormat == paInt8)
-        device->FmtType = DevFmtByte;
-    else if(self->params.sampleFormat == paUInt8)
-        device->FmtType = DevFmtUByte;
-    else if(self->params.sampleFormat == paInt16)
-        device->FmtType = DevFmtShort;
-    else if(self->params.sampleFormat == paInt32)
-        device->FmtType = DevFmtInt;
-    else if(self->params.sampleFormat == paFloat32)
-        device->FmtType = DevFmtFloat;
-    else
-    {
-        ERR("Unexpected sample format: 0x%lx\n", self->params.sampleFormat);
-        return ALC_FALSE;
-    }
-
-    if(self->params.channelCount == 2)
-        device->FmtChans = DevFmtStereo;
-    else if(self->params.channelCount == 1)
-        device->FmtChans = DevFmtMono;
-    else
-    {
-        ERR("Unexpected channel count: %u\n", self->params.channelCount);
-        return ALC_FALSE;
-    }
-    SetDefaultChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCportPlayback_start(ALCportPlayback *self)
-{
-    PaError err;
-
-    err = Pa_StartStream(self->stream);
-    if(err != paNoError)
-    {
-        ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCportPlayback_stop(ALCportPlayback *self)
-{
-    PaError err = Pa_StopStream(self->stream);
-    if(err != paNoError)
-        ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
-}
-
-
-typedef struct ALCportCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    PaStream *stream;
-    PaStreamParameters params;
-
-    ll_ringbuffer_t *ring;
-} ALCportCapture;
-
-static int ALCportCapture_ReadCallback(const void *inputBuffer, void *outputBuffer,
-    unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
-    const PaStreamCallbackFlags statusFlags, void *userData);
-
-static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device);
-static void ALCportCapture_Destruct(ALCportCapture *self);
-static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCportCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCportCapture_start(ALCportCapture *self);
-static void ALCportCapture_stop(ALCportCapture *self);
-static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCportCapture_availableSamples(ALCportCapture *self);
-static DECLARE_FORWARD(ALCportCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCportCapture)
-
-DEFINE_ALCBACKEND_VTABLE(ALCportCapture);
-
-
-static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCportCapture, ALCbackend, self);
-
-    self->stream = NULL;
-    self->ring = NULL;
-}
-
-static void ALCportCapture_Destruct(ALCportCapture *self)
-{
-    PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError;
-    if(err != paNoError)
-        ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
-    self->stream = NULL;
-
-    ll_ringbuffer_free(self->ring);
-    self->ring = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCportCapture_ReadCallback(const void *inputBuffer, void *UNUSED(outputBuffer),
-    unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo),
-    const PaStreamCallbackFlags UNUSED(statusFlags), void *userData)
-{
-    ALCportCapture *self = userData;
-    size_t writable = ll_ringbuffer_write_space(self->ring);
-
-    if(framesPerBuffer > writable)
-        framesPerBuffer = writable;
-    ll_ringbuffer_write(self->ring, inputBuffer, framesPerBuffer);
-    return 0;
-}
-
-
-static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALuint samples, frame_size;
-    PaError err;
-
-    if(!name)
-        name = pa_device;
-    else if(strcmp(name, pa_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    samples = device->UpdateSize * device->NumUpdates;
-    samples = maxu(samples, 100 * device->Frequency / 1000);
-    frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    self->ring = ll_ringbuffer_create(samples, frame_size, false);
-    if(self->ring == NULL) return ALC_INVALID_VALUE;
-
-    self->params.device = -1;
-    if(!ConfigValueInt(NULL, "port", "capture", &self->params.device) ||
-       self->params.device < 0)
-        self->params.device = Pa_GetDefaultInputDevice();
-    self->params.suggestedLatency = 0.0f;
-    self->params.hostApiSpecificStreamInfo = NULL;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            self->params.sampleFormat = paInt8;
-            break;
-        case DevFmtUByte:
-            self->params.sampleFormat = paUInt8;
-            break;
-        case DevFmtShort:
-            self->params.sampleFormat = paInt16;
-            break;
-        case DevFmtInt:
-            self->params.sampleFormat = paInt32;
-            break;
-        case DevFmtFloat:
-            self->params.sampleFormat = paFloat32;
-            break;
-        case DevFmtUInt:
-        case DevFmtUShort:
-            ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType));
-            return ALC_INVALID_VALUE;
-    }
-    self->params.channelCount = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
-    err = Pa_OpenStream(&self->stream, &self->params, NULL,
-        device->Frequency, paFramesPerBufferUnspecified, paNoFlag,
-        ALCportCapture_ReadCallback, self
-    );
-    if(err != paNoError)
-    {
-        ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-
-static ALCboolean ALCportCapture_start(ALCportCapture *self)
-{
-    PaError err = Pa_StartStream(self->stream);
-    if(err != paNoError)
-    {
-        ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
-        return ALC_FALSE;
-    }
-    return ALC_TRUE;
-}
-
-static void ALCportCapture_stop(ALCportCapture *self)
-{
-    PaError err = Pa_StopStream(self->stream);
-    if(err != paNoError)
-        ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
-}
-
-
-static ALCuint ALCportCapture_availableSamples(ALCportCapture *self)
-{
-    return ll_ringbuffer_read_space(self->ring);
-}
-
-static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    ll_ringbuffer_read(self->ring, buffer, samples);
-    return ALC_NO_ERROR;
-}
-
-
-typedef struct ALCportBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCportBackendFactory;
-#define ALCPORTBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCportBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory *self);
-static void ALCportBackendFactory_deinit(ALCportBackendFactory *self);
-static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory *self, ALCbackend_Type type);
-static void ALCportBackendFactory_probe(ALCportBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCportBackendFactory);
-
-
-static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory* UNUSED(self))
-{
-    if(!pa_load())
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCportBackendFactory_deinit(ALCportBackendFactory* UNUSED(self))
-{
-#ifdef HAVE_DYNLOAD
-    if(pa_handle)
-    {
-        Pa_Terminate();
-        CloseLib(pa_handle);
-        pa_handle = NULL;
-    }
-#else
-    Pa_Terminate();
-#endif
-}
-
-static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCportBackendFactory_probe(ALCportBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(pa_device);
-            break;
-        case CAPTURE_DEVICE_PROBE:
-            AppendCaptureDeviceList(pa_device);
-            break;
-    }
-}
-
-static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCportPlayback *backend;
-        NEW_OBJ(backend, ALCportPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCportCapture *backend;
-        NEW_OBJ(backend, ALCportCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-ALCbackendFactory *ALCportBackendFactory_getFactory(void)
-{
-    static ALCportBackendFactory factory = ALCPORTBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 442 - 0
Engine/lib/openal-soft/Alc/backends/portaudio.cpp

@@ -0,0 +1,442 @@
+/**
+ * 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 "backends/portaudio.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+
+#include <portaudio.h>
+
+
+namespace {
+
+constexpr char pa_device[] = "PortAudio Default";
+
+
+#ifdef HAVE_DYNLOAD
+void *pa_handle;
+#define MAKE_FUNC(x) decltype(x) * p##x
+MAKE_FUNC(Pa_Initialize);
+MAKE_FUNC(Pa_Terminate);
+MAKE_FUNC(Pa_GetErrorText);
+MAKE_FUNC(Pa_StartStream);
+MAKE_FUNC(Pa_StopStream);
+MAKE_FUNC(Pa_OpenStream);
+MAKE_FUNC(Pa_CloseStream);
+MAKE_FUNC(Pa_GetDefaultOutputDevice);
+MAKE_FUNC(Pa_GetDefaultInputDevice);
+MAKE_FUNC(Pa_GetStreamInfo);
+#undef MAKE_FUNC
+
+#ifndef IN_IDE_PARSER
+#define Pa_Initialize                  pPa_Initialize
+#define Pa_Terminate                   pPa_Terminate
+#define Pa_GetErrorText                pPa_GetErrorText
+#define Pa_StartStream                 pPa_StartStream
+#define Pa_StopStream                  pPa_StopStream
+#define Pa_OpenStream                  pPa_OpenStream
+#define Pa_CloseStream                 pPa_CloseStream
+#define Pa_GetDefaultOutputDevice      pPa_GetDefaultOutputDevice
+#define Pa_GetDefaultInputDevice       pPa_GetDefaultInputDevice
+#define Pa_GetStreamInfo               pPa_GetStreamInfo
+#endif
+#endif
+
+
+struct PortPlayback final : public BackendBase {
+    PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~PortPlayback() override;
+
+    int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
+        const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
+    static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
+        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
+    {
+        return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
+            framesPerBuffer, timeInfo, statusFlags);
+    }
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    PaStream *mStream{nullptr};
+    PaStreamParameters mParams{};
+    uint mUpdateSize{0u};
+
+    DEF_NEWDEL(PortPlayback)
+};
+
+PortPlayback::~PortPlayback()
+{
+    PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
+    if(err != paNoError)
+        ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
+    mStream = nullptr;
+}
+
+
+int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
+    const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
+{
+    mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
+        static_cast<uint>(mParams.channelCount));
+    return 0;
+}
+
+
+void PortPlayback::open(const char *name)
+{
+    if(!name)
+        name = pa_device;
+    else if(strcmp(name, pa_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    mUpdateSize = mDevice->UpdateSize;
+
+    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;
+
+    mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        mParams.sampleFormat = paInt8;
+        break;
+    case DevFmtUByte:
+        mParams.sampleFormat = paUInt8;
+        break;
+    case DevFmtUShort:
+        /* fall-through */
+    case DevFmtShort:
+        mParams.sampleFormat = paInt16;
+        break;
+    case DevFmtUInt:
+        /* fall-through */
+    case DevFmtInt:
+        mParams.sampleFormat = paInt32;
+        break;
+    case DevFmtFloat:
+        mParams.sampleFormat = paFloat32;
+        break;
+    }
+
+retry_open:
+    PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
+        paNoFlag, &PortPlayback::writeCallbackC, this)};
+    if(err != paNoError)
+    {
+        if(mParams.sampleFormat == paFloat32)
+        {
+            mParams.sampleFormat = paInt16;
+            goto retry_open;
+        }
+        throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
+            Pa_GetErrorText(err)};
+    }
+
+    mDevice->DeviceName = name;
+}
+
+bool PortPlayback::reset()
+{
+    const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
+    mDevice->Frequency = static_cast<uint>(streamInfo->sampleRate);
+    mDevice->UpdateSize = mUpdateSize;
+
+    if(mParams.sampleFormat == paInt8)
+        mDevice->FmtType = DevFmtByte;
+    else if(mParams.sampleFormat == paUInt8)
+        mDevice->FmtType = DevFmtUByte;
+    else if(mParams.sampleFormat == paInt16)
+        mDevice->FmtType = DevFmtShort;
+    else if(mParams.sampleFormat == paInt32)
+        mDevice->FmtType = DevFmtInt;
+    else if(mParams.sampleFormat == paFloat32)
+        mDevice->FmtType = DevFmtFloat;
+    else
+    {
+        ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
+        return false;
+    }
+
+    if(mParams.channelCount == 2)
+        mDevice->FmtChans = DevFmtStereo;
+    else if(mParams.channelCount == 1)
+        mDevice->FmtChans = DevFmtMono;
+    else
+    {
+        ERR("Unexpected channel count: %u\n", mParams.channelCount);
+        return false;
+    }
+    setDefaultChannelOrder();
+
+    return true;
+}
+
+void PortPlayback::start()
+{
+    const PaError err{Pa_StartStream(mStream)};
+    if(err == paNoError)
+        throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
+            Pa_GetErrorText(err)};
+}
+
+void PortPlayback::stop()
+{
+    PaError err{Pa_StopStream(mStream)};
+    if(err != paNoError)
+        ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
+}
+
+
+struct PortCapture final : public BackendBase {
+    PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~PortCapture() override;
+
+    int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
+        const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
+    static int readCallbackC(const void *inputBuffer, void *outputBuffer,
+        unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+        const PaStreamCallbackFlags statusFlags, void *userData) noexcept
+    {
+        return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
+            framesPerBuffer, timeInfo, statusFlags);
+    }
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    PaStream *mStream{nullptr};
+    PaStreamParameters mParams;
+
+    RingBufferPtr mRing{nullptr};
+
+    DEF_NEWDEL(PortCapture)
+};
+
+PortCapture::~PortCapture()
+{
+    PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
+    if(err != paNoError)
+        ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
+    mStream = nullptr;
+}
+
+
+int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
+    const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
+{
+    mRing->write(inputBuffer, framesPerBuffer);
+    return 0;
+}
+
+
+void PortCapture::open(const char *name)
+{
+    if(!name)
+        name = pa_device;
+    else if(strcmp(name, pa_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    uint samples{mDevice->BufferSize};
+    samples = maxu(samples, 100 * mDevice->Frequency / 1000);
+    uint frame_size{mDevice->frameSizeFromFmt()};
+
+    mRing = RingBuffer::Create(samples, frame_size, false);
+
+    auto devidopt = ConfigValueInt(nullptr, "port", "capture");
+    if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
+    else mParams.device = Pa_GetDefaultOutputDevice();
+    mParams.suggestedLatency = 0.0f;
+    mParams.hostApiSpecificStreamInfo = nullptr;
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        mParams.sampleFormat = paInt8;
+        break;
+    case DevFmtUByte:
+        mParams.sampleFormat = paUInt8;
+        break;
+    case DevFmtShort:
+        mParams.sampleFormat = paInt16;
+        break;
+    case DevFmtInt:
+        mParams.sampleFormat = paInt32;
+        break;
+    case DevFmtFloat:
+        mParams.sampleFormat = paFloat32;
+        break;
+    case DevFmtUInt:
+    case DevFmtUShort:
+        throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
+            DevFmtTypeString(mDevice->FmtType)};
+    }
+    mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
+
+    PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
+        paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
+    if(err != paNoError)
+        throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
+            Pa_GetErrorText(err)};
+
+    mDevice->DeviceName = name;
+}
+
+
+void PortCapture::start()
+{
+    const PaError err{Pa_StartStream(mStream)};
+    if(err != paNoError)
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start recording: %s", Pa_GetErrorText(err)};
+}
+
+void PortCapture::stop()
+{
+    PaError err{Pa_StopStream(mStream)};
+    if(err != paNoError)
+        ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
+}
+
+
+uint PortCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+void PortCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+} // namespace
+
+
+bool PortBackendFactory::init()
+{
+    PaError err;
+
+#ifdef HAVE_DYNLOAD
+    if(!pa_handle)
+    {
+#ifdef _WIN32
+# define PALIB "portaudio.dll"
+#elif defined(__APPLE__) && defined(__MACH__)
+# define PALIB "libportaudio.2.dylib"
+#elif defined(__OpenBSD__)
+# define PALIB "libportaudio.so"
+#else
+# define PALIB "libportaudio.so.2"
+#endif
+
+        pa_handle = LoadLib(PALIB);
+        if(!pa_handle)
+            return false;
+
+#define LOAD_FUNC(f) do {                                                     \
+    p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f));        \
+    if(p##f == nullptr)                                                       \
+    {                                                                         \
+        CloseLib(pa_handle);                                                  \
+        pa_handle = nullptr;                                                  \
+        return false;                                                         \
+    }                                                                         \
+} while(0)
+        LOAD_FUNC(Pa_Initialize);
+        LOAD_FUNC(Pa_Terminate);
+        LOAD_FUNC(Pa_GetErrorText);
+        LOAD_FUNC(Pa_StartStream);
+        LOAD_FUNC(Pa_StopStream);
+        LOAD_FUNC(Pa_OpenStream);
+        LOAD_FUNC(Pa_CloseStream);
+        LOAD_FUNC(Pa_GetDefaultOutputDevice);
+        LOAD_FUNC(Pa_GetDefaultInputDevice);
+        LOAD_FUNC(Pa_GetStreamInfo);
+#undef LOAD_FUNC
+
+        if((err=Pa_Initialize()) != paNoError)
+        {
+            ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
+            CloseLib(pa_handle);
+            pa_handle = nullptr;
+            return false;
+        }
+    }
+#else
+    if((err=Pa_Initialize()) != paNoError)
+    {
+        ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
+        return false;
+    }
+#endif
+    return true;
+}
+
+bool PortBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string PortBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+    case BackendType::Capture:
+        /* Includes null char. */
+        outnames.append(pa_device, sizeof(pa_device));
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new PortPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new PortCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &PortBackendFactory::getFactory()
+{
+    static PortBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/portaudio.h

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

+ 0 - 1919
Engine/lib/openal-soft/Alc/backends/pulseaudio.c

@@ -1,1919 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2009 by Konstantinos Natsakis <[email protected]>
- * Copyright (C) 2010 by Chris Robinson <[email protected]>
- * 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 <string.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <pulse/pulseaudio.h>
-
-#if PA_API_VERSION == 12
-
-#ifdef HAVE_DYNLOAD
-static void *pa_handle;
-#define MAKE_FUNC(x) static __typeof(x) * p##x
-MAKE_FUNC(pa_context_unref);
-MAKE_FUNC(pa_sample_spec_valid);
-MAKE_FUNC(pa_frame_size);
-MAKE_FUNC(pa_stream_drop);
-MAKE_FUNC(pa_strerror);
-MAKE_FUNC(pa_context_get_state);
-MAKE_FUNC(pa_stream_get_state);
-MAKE_FUNC(pa_threaded_mainloop_signal);
-MAKE_FUNC(pa_stream_peek);
-MAKE_FUNC(pa_threaded_mainloop_wait);
-MAKE_FUNC(pa_threaded_mainloop_unlock);
-MAKE_FUNC(pa_threaded_mainloop_in_thread);
-MAKE_FUNC(pa_context_new);
-MAKE_FUNC(pa_threaded_mainloop_stop);
-MAKE_FUNC(pa_context_disconnect);
-MAKE_FUNC(pa_threaded_mainloop_start);
-MAKE_FUNC(pa_threaded_mainloop_get_api);
-MAKE_FUNC(pa_context_set_state_callback);
-MAKE_FUNC(pa_stream_write);
-MAKE_FUNC(pa_xfree);
-MAKE_FUNC(pa_stream_connect_record);
-MAKE_FUNC(pa_stream_connect_playback);
-MAKE_FUNC(pa_stream_readable_size);
-MAKE_FUNC(pa_stream_writable_size);
-MAKE_FUNC(pa_stream_is_corked);
-MAKE_FUNC(pa_stream_cork);
-MAKE_FUNC(pa_stream_is_suspended);
-MAKE_FUNC(pa_stream_get_device_name);
-MAKE_FUNC(pa_stream_get_latency);
-MAKE_FUNC(pa_path_get_filename);
-MAKE_FUNC(pa_get_binary_name);
-MAKE_FUNC(pa_threaded_mainloop_free);
-MAKE_FUNC(pa_context_errno);
-MAKE_FUNC(pa_xmalloc);
-MAKE_FUNC(pa_stream_unref);
-MAKE_FUNC(pa_threaded_mainloop_accept);
-MAKE_FUNC(pa_stream_set_write_callback);
-MAKE_FUNC(pa_threaded_mainloop_new);
-MAKE_FUNC(pa_context_connect);
-MAKE_FUNC(pa_stream_set_buffer_attr);
-MAKE_FUNC(pa_stream_get_buffer_attr);
-MAKE_FUNC(pa_stream_get_sample_spec);
-MAKE_FUNC(pa_stream_get_time);
-MAKE_FUNC(pa_stream_set_read_callback);
-MAKE_FUNC(pa_stream_set_state_callback);
-MAKE_FUNC(pa_stream_set_moved_callback);
-MAKE_FUNC(pa_stream_set_underflow_callback);
-MAKE_FUNC(pa_stream_new_with_proplist);
-MAKE_FUNC(pa_stream_disconnect);
-MAKE_FUNC(pa_threaded_mainloop_lock);
-MAKE_FUNC(pa_channel_map_init_auto);
-MAKE_FUNC(pa_channel_map_parse);
-MAKE_FUNC(pa_channel_map_snprint);
-MAKE_FUNC(pa_channel_map_equal);
-MAKE_FUNC(pa_context_get_server_info);
-MAKE_FUNC(pa_context_get_sink_info_by_name);
-MAKE_FUNC(pa_context_get_sink_info_list);
-MAKE_FUNC(pa_context_get_source_info_by_name);
-MAKE_FUNC(pa_context_get_source_info_list);
-MAKE_FUNC(pa_operation_get_state);
-MAKE_FUNC(pa_operation_unref);
-MAKE_FUNC(pa_proplist_new);
-MAKE_FUNC(pa_proplist_free);
-MAKE_FUNC(pa_proplist_set);
-MAKE_FUNC(pa_channel_map_superset);
-MAKE_FUNC(pa_stream_set_buffer_attr_callback);
-MAKE_FUNC(pa_stream_begin_write);
-#undef MAKE_FUNC
-
-#define pa_context_unref ppa_context_unref
-#define pa_sample_spec_valid ppa_sample_spec_valid
-#define pa_frame_size ppa_frame_size
-#define pa_stream_drop ppa_stream_drop
-#define pa_strerror ppa_strerror
-#define pa_context_get_state ppa_context_get_state
-#define pa_stream_get_state ppa_stream_get_state
-#define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal
-#define pa_stream_peek ppa_stream_peek
-#define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait
-#define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock
-#define pa_threaded_mainloop_in_thread ppa_threaded_mainloop_in_thread
-#define pa_context_new ppa_context_new
-#define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop
-#define pa_context_disconnect ppa_context_disconnect
-#define pa_threaded_mainloop_start ppa_threaded_mainloop_start
-#define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api
-#define pa_context_set_state_callback ppa_context_set_state_callback
-#define pa_stream_write ppa_stream_write
-#define pa_xfree ppa_xfree
-#define pa_stream_connect_record ppa_stream_connect_record
-#define pa_stream_connect_playback ppa_stream_connect_playback
-#define pa_stream_readable_size ppa_stream_readable_size
-#define pa_stream_writable_size ppa_stream_writable_size
-#define pa_stream_is_corked ppa_stream_is_corked
-#define pa_stream_cork ppa_stream_cork
-#define pa_stream_is_suspended ppa_stream_is_suspended
-#define pa_stream_get_device_name ppa_stream_get_device_name
-#define pa_stream_get_latency ppa_stream_get_latency
-#define pa_path_get_filename ppa_path_get_filename
-#define pa_get_binary_name ppa_get_binary_name
-#define pa_threaded_mainloop_free ppa_threaded_mainloop_free
-#define pa_context_errno ppa_context_errno
-#define pa_xmalloc ppa_xmalloc
-#define pa_stream_unref ppa_stream_unref
-#define pa_threaded_mainloop_accept ppa_threaded_mainloop_accept
-#define pa_stream_set_write_callback ppa_stream_set_write_callback
-#define pa_threaded_mainloop_new ppa_threaded_mainloop_new
-#define pa_context_connect ppa_context_connect
-#define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr
-#define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr
-#define pa_stream_get_sample_spec ppa_stream_get_sample_spec
-#define pa_stream_get_time ppa_stream_get_time
-#define pa_stream_set_read_callback ppa_stream_set_read_callback
-#define pa_stream_set_state_callback ppa_stream_set_state_callback
-#define pa_stream_set_moved_callback ppa_stream_set_moved_callback
-#define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback
-#define pa_stream_new_with_proplist ppa_stream_new_with_proplist
-#define pa_stream_disconnect ppa_stream_disconnect
-#define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock
-#define pa_channel_map_init_auto ppa_channel_map_init_auto
-#define pa_channel_map_parse ppa_channel_map_parse
-#define pa_channel_map_snprint ppa_channel_map_snprint
-#define pa_channel_map_equal ppa_channel_map_equal
-#define pa_context_get_server_info ppa_context_get_server_info
-#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
-#define pa_context_get_sink_info_list ppa_context_get_sink_info_list
-#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
-#define pa_context_get_source_info_list ppa_context_get_source_info_list
-#define pa_operation_get_state ppa_operation_get_state
-#define pa_operation_unref ppa_operation_unref
-#define pa_proplist_new ppa_proplist_new
-#define pa_proplist_free ppa_proplist_free
-#define pa_proplist_set ppa_proplist_set
-#define pa_channel_map_superset ppa_channel_map_superset
-#define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback
-#define pa_stream_begin_write ppa_stream_begin_write
-
-#endif
-
-static ALCboolean pulse_load(void)
-{
-    ALCboolean ret = ALC_TRUE;
-#ifdef HAVE_DYNLOAD
-    if(!pa_handle)
-    {
-        al_string missing_funcs = AL_STRING_INIT_STATIC();
-
-#ifdef _WIN32
-#define PALIB "libpulse-0.dll"
-#elif defined(__APPLE__) && defined(__MACH__)
-#define PALIB "libpulse.0.dylib"
-#else
-#define PALIB "libpulse.so.0"
-#endif
-        pa_handle = LoadLib(PALIB);
-        if(!pa_handle)
-        {
-            WARN("Failed to load %s\n", PALIB);
-            return ALC_FALSE;
-        }
-
-#define LOAD_FUNC(x) do {                                                     \
-    p##x = GetSymbol(pa_handle, #x);                                          \
-    if(!(p##x)) {                                                             \
-        ret = ALC_FALSE;                                                      \
-        alstr_append_cstr(&missing_funcs, "\n" #x);                           \
-    }                                                                         \
-} while(0)
-        LOAD_FUNC(pa_context_unref);
-        LOAD_FUNC(pa_sample_spec_valid);
-        LOAD_FUNC(pa_stream_drop);
-        LOAD_FUNC(pa_frame_size);
-        LOAD_FUNC(pa_strerror);
-        LOAD_FUNC(pa_context_get_state);
-        LOAD_FUNC(pa_stream_get_state);
-        LOAD_FUNC(pa_threaded_mainloop_signal);
-        LOAD_FUNC(pa_stream_peek);
-        LOAD_FUNC(pa_threaded_mainloop_wait);
-        LOAD_FUNC(pa_threaded_mainloop_unlock);
-        LOAD_FUNC(pa_threaded_mainloop_in_thread);
-        LOAD_FUNC(pa_context_new);
-        LOAD_FUNC(pa_threaded_mainloop_stop);
-        LOAD_FUNC(pa_context_disconnect);
-        LOAD_FUNC(pa_threaded_mainloop_start);
-        LOAD_FUNC(pa_threaded_mainloop_get_api);
-        LOAD_FUNC(pa_context_set_state_callback);
-        LOAD_FUNC(pa_stream_write);
-        LOAD_FUNC(pa_xfree);
-        LOAD_FUNC(pa_stream_connect_record);
-        LOAD_FUNC(pa_stream_connect_playback);
-        LOAD_FUNC(pa_stream_readable_size);
-        LOAD_FUNC(pa_stream_writable_size);
-        LOAD_FUNC(pa_stream_is_corked);
-        LOAD_FUNC(pa_stream_cork);
-        LOAD_FUNC(pa_stream_is_suspended);
-        LOAD_FUNC(pa_stream_get_device_name);
-        LOAD_FUNC(pa_stream_get_latency);
-        LOAD_FUNC(pa_path_get_filename);
-        LOAD_FUNC(pa_get_binary_name);
-        LOAD_FUNC(pa_threaded_mainloop_free);
-        LOAD_FUNC(pa_context_errno);
-        LOAD_FUNC(pa_xmalloc);
-        LOAD_FUNC(pa_stream_unref);
-        LOAD_FUNC(pa_threaded_mainloop_accept);
-        LOAD_FUNC(pa_stream_set_write_callback);
-        LOAD_FUNC(pa_threaded_mainloop_new);
-        LOAD_FUNC(pa_context_connect);
-        LOAD_FUNC(pa_stream_set_buffer_attr);
-        LOAD_FUNC(pa_stream_get_buffer_attr);
-        LOAD_FUNC(pa_stream_get_sample_spec);
-        LOAD_FUNC(pa_stream_get_time);
-        LOAD_FUNC(pa_stream_set_read_callback);
-        LOAD_FUNC(pa_stream_set_state_callback);
-        LOAD_FUNC(pa_stream_set_moved_callback);
-        LOAD_FUNC(pa_stream_set_underflow_callback);
-        LOAD_FUNC(pa_stream_new_with_proplist);
-        LOAD_FUNC(pa_stream_disconnect);
-        LOAD_FUNC(pa_threaded_mainloop_lock);
-        LOAD_FUNC(pa_channel_map_init_auto);
-        LOAD_FUNC(pa_channel_map_parse);
-        LOAD_FUNC(pa_channel_map_snprint);
-        LOAD_FUNC(pa_channel_map_equal);
-        LOAD_FUNC(pa_context_get_server_info);
-        LOAD_FUNC(pa_context_get_sink_info_by_name);
-        LOAD_FUNC(pa_context_get_sink_info_list);
-        LOAD_FUNC(pa_context_get_source_info_by_name);
-        LOAD_FUNC(pa_context_get_source_info_list);
-        LOAD_FUNC(pa_operation_get_state);
-        LOAD_FUNC(pa_operation_unref);
-        LOAD_FUNC(pa_proplist_new);
-        LOAD_FUNC(pa_proplist_free);
-        LOAD_FUNC(pa_proplist_set);
-        LOAD_FUNC(pa_channel_map_superset);
-        LOAD_FUNC(pa_stream_set_buffer_attr_callback);
-        LOAD_FUNC(pa_stream_begin_write);
-#undef LOAD_FUNC
-
-        if(ret == ALC_FALSE)
-        {
-            WARN("Missing expected functions:%s\n", alstr_get_cstr(missing_funcs));
-            CloseLib(pa_handle);
-            pa_handle = NULL;
-        }
-        alstr_reset(&missing_funcs);
-    }
-#endif /* HAVE_DYNLOAD */
-    return ret;
-}
-
-
-/* Global flags and properties */
-static pa_context_flags_t pulse_ctx_flags;
-static pa_proplist *prop_filter;
-
-
-/* PulseAudio Event Callbacks */
-static void context_state_callback(pa_context *context, void *pdata)
-{
-    pa_threaded_mainloop *loop = pdata;
-    pa_context_state_t state;
-
-    state = pa_context_get_state(context);
-    if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state))
-        pa_threaded_mainloop_signal(loop, 0);
-}
-
-static void stream_state_callback(pa_stream *stream, void *pdata)
-{
-    pa_threaded_mainloop *loop = pdata;
-    pa_stream_state_t state;
-
-    state = pa_stream_get_state(stream);
-    if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state))
-        pa_threaded_mainloop_signal(loop, 0);
-}
-
-static void stream_success_callback(pa_stream *UNUSED(stream), int UNUSED(success), void *pdata)
-{
-    pa_threaded_mainloop *loop = pdata;
-    pa_threaded_mainloop_signal(loop, 0);
-}
-
-static void wait_for_operation(pa_operation *op, pa_threaded_mainloop *loop)
-{
-    if(op)
-    {
-        while(pa_operation_get_state(op) == PA_OPERATION_RUNNING)
-            pa_threaded_mainloop_wait(loop);
-        pa_operation_unref(op);
-    }
-}
-
-
-static pa_context *connect_context(pa_threaded_mainloop *loop, ALboolean silent)
-{
-    const char *name = "OpenAL Soft";
-    al_string binname = AL_STRING_INIT_STATIC();
-    pa_context_state_t state;
-    pa_context *context;
-    int err;
-
-    GetProcBinary(NULL, &binname);
-    if(!alstr_empty(binname))
-        name = alstr_get_cstr(binname);
-
-    context = pa_context_new(pa_threaded_mainloop_get_api(loop), name);
-    if(!context)
-    {
-        ERR("pa_context_new() failed\n");
-        alstr_reset(&binname);
-        return NULL;
-    }
-
-    pa_context_set_state_callback(context, context_state_callback, loop);
-
-    if((err=pa_context_connect(context, NULL, pulse_ctx_flags, NULL)) >= 0)
-    {
-        while((state=pa_context_get_state(context)) != PA_CONTEXT_READY)
-        {
-            if(!PA_CONTEXT_IS_GOOD(state))
-            {
-                err = pa_context_errno(context);
-                if(err > 0)  err = -err;
-                break;
-            }
-
-            pa_threaded_mainloop_wait(loop);
-        }
-    }
-    pa_context_set_state_callback(context, NULL, NULL);
-
-    if(err < 0)
-    {
-        if(!silent)
-            ERR("Context did not connect: %s\n", pa_strerror(err));
-        pa_context_unref(context);
-        context = NULL;
-    }
-
-    alstr_reset(&binname);
-    return context;
-}
-
-
-static ALCboolean pulse_open(pa_threaded_mainloop **loop, pa_context **context,
-                             void(*state_cb)(pa_context*,void*), void *ptr)
-{
-    if(!(*loop = pa_threaded_mainloop_new()))
-    {
-        ERR("pa_threaded_mainloop_new() failed!\n");
-        return ALC_FALSE;
-    }
-    if(pa_threaded_mainloop_start(*loop) < 0)
-    {
-        ERR("pa_threaded_mainloop_start() failed\n");
-        goto error;
-    }
-
-    pa_threaded_mainloop_lock(*loop);
-
-    *context = connect_context(*loop, AL_FALSE);
-    if(!*context)
-    {
-        pa_threaded_mainloop_unlock(*loop);
-        pa_threaded_mainloop_stop(*loop);
-        goto error;
-    }
-    pa_context_set_state_callback(*context, state_cb, ptr);
-
-    pa_threaded_mainloop_unlock(*loop);
-    return ALC_TRUE;
-
-error:
-    pa_threaded_mainloop_free(*loop);
-    *loop = NULL;
-
-    return ALC_FALSE;
-}
-
-static void pulse_close(pa_threaded_mainloop *loop, pa_context *context, pa_stream *stream)
-{
-    pa_threaded_mainloop_lock(loop);
-
-    if(stream)
-    {
-        pa_stream_set_state_callback(stream, NULL, NULL);
-        pa_stream_set_moved_callback(stream, NULL, NULL);
-        pa_stream_set_write_callback(stream, NULL, NULL);
-        pa_stream_set_buffer_attr_callback(stream, NULL, NULL);
-        pa_stream_disconnect(stream);
-        pa_stream_unref(stream);
-    }
-
-    pa_context_disconnect(context);
-    pa_context_unref(context);
-
-    pa_threaded_mainloop_unlock(loop);
-
-    pa_threaded_mainloop_stop(loop);
-    pa_threaded_mainloop_free(loop);
-}
-
-
-typedef struct {
-    al_string name;
-    al_string device_name;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
-
-static vector_DevMap PlaybackDevices;
-static vector_DevMap CaptureDevices;
-
-static void clear_devlist(vector_DevMap *list)
-{
-#define DEINIT_STRS(i)  (AL_STRING_DEINIT((i)->name),AL_STRING_DEINIT((i)->device_name))
-    VECTOR_FOR_EACH(DevMap, *list, DEINIT_STRS);
-#undef DEINIT_STRS
-    VECTOR_RESIZE(*list, 0, 0);
-}
-
-
-typedef struct ALCpulsePlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    al_string device_name;
-
-    pa_buffer_attr attr;
-    pa_sample_spec spec;
-
-    pa_threaded_mainloop *loop;
-
-    pa_stream *stream;
-    pa_context *context;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCpulsePlayback;
-
-static void ALCpulsePlayback_deviceCallback(pa_context *context, const pa_sink_info *info, int eol, void *pdata);
-static void ALCpulsePlayback_probeDevices(void);
-
-static void ALCpulsePlayback_bufferAttrCallback(pa_stream *stream, void *pdata);
-static void ALCpulsePlayback_contextStateCallback(pa_context *context, void *pdata);
-static void ALCpulsePlayback_streamStateCallback(pa_stream *stream, void *pdata);
-static void ALCpulsePlayback_streamWriteCallback(pa_stream *p, size_t nbytes, void *userdata);
-static void ALCpulsePlayback_sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol, void *pdata);
-static void ALCpulsePlayback_sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol, void *pdata);
-static void ALCpulsePlayback_streamMovedCallback(pa_stream *stream, void *pdata);
-static pa_stream *ALCpulsePlayback_connectStream(const char *device_name, pa_threaded_mainloop *loop,
-                                                 pa_context *context, pa_stream_flags_t flags,
-                                                 pa_buffer_attr *attr, pa_sample_spec *spec,
-                                                 pa_channel_map *chanmap);
-static int ALCpulsePlayback_mixerProc(void *ptr);
-
-static void ALCpulsePlayback_Construct(ALCpulsePlayback *self, ALCdevice *device);
-static void ALCpulsePlayback_Destruct(ALCpulsePlayback *self);
-static ALCenum ALCpulsePlayback_open(ALCpulsePlayback *self, const ALCchar *name);
-static ALCboolean ALCpulsePlayback_reset(ALCpulsePlayback *self);
-static ALCboolean ALCpulsePlayback_start(ALCpulsePlayback *self);
-static void ALCpulsePlayback_stop(ALCpulsePlayback *self);
-static DECLARE_FORWARD2(ALCpulsePlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
-static DECLARE_FORWARD(ALCpulsePlayback, ALCbackend, ALCuint, availableSamples)
-static ClockLatency ALCpulsePlayback_getClockLatency(ALCpulsePlayback *self);
-static void ALCpulsePlayback_lock(ALCpulsePlayback *self);
-static void ALCpulsePlayback_unlock(ALCpulsePlayback *self);
-DECLARE_DEFAULT_ALLOCATORS(ALCpulsePlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCpulsePlayback);
-
-
-static void ALCpulsePlayback_Construct(ALCpulsePlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCpulsePlayback, ALCbackend, self);
-
-    self->loop = NULL;
-    AL_STRING_INIT(self->device_name);
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCpulsePlayback_Destruct(ALCpulsePlayback *self)
-{
-    if(self->loop)
-    {
-        pulse_close(self->loop, self->context, self->stream);
-        self->loop = NULL;
-        self->context = NULL;
-        self->stream = NULL;
-    }
-    AL_STRING_DEINIT(self->device_name);
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static void ALCpulsePlayback_deviceCallback(pa_context *UNUSED(context), const pa_sink_info *info, int eol, void *pdata)
-{
-    pa_threaded_mainloop *loop = pdata;
-    const DevMap *iter;
-    DevMap entry;
-    int count;
-
-    if(eol)
-    {
-        pa_threaded_mainloop_signal(loop, 0);
-        return;
-    }
-
-#define MATCH_INFO_NAME(iter) (alstr_cmp_cstr((iter)->device_name, info->name) == 0)
-    VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_INFO_NAME);
-    if(iter != VECTOR_END(PlaybackDevices)) return;
-#undef MATCH_INFO_NAME
-
-    AL_STRING_INIT(entry.name);
-    AL_STRING_INIT(entry.device_name);
-
-    alstr_copy_cstr(&entry.device_name, info->name);
-
-    count = 0;
-    while(1)
-    {
-        alstr_copy_cstr(&entry.name, info->description);
-        if(count != 0)
-        {
-            char str[64];
-            snprintf(str, sizeof(str), " #%d", count+1);
-            alstr_append_cstr(&entry.name, str);
-        }
-
-#define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_ENTRY);
-        if(iter == VECTOR_END(PlaybackDevices)) break;
-#undef MATCH_ENTRY
-        count++;
-    }
-
-    TRACE("Got device \"%s\", \"%s\"\n", alstr_get_cstr(entry.name), alstr_get_cstr(entry.device_name));
-
-    VECTOR_PUSH_BACK(PlaybackDevices, entry);
-}
-
-static void ALCpulsePlayback_probeDevices(void)
-{
-    pa_threaded_mainloop *loop;
-
-    clear_devlist(&PlaybackDevices);
-
-    if((loop=pa_threaded_mainloop_new()) &&
-       pa_threaded_mainloop_start(loop) >= 0)
-    {
-        pa_context *context;
-
-        pa_threaded_mainloop_lock(loop);
-        context = connect_context(loop, AL_FALSE);
-        if(context)
-        {
-            pa_operation *o;
-            pa_stream_flags_t flags;
-            pa_sample_spec spec;
-            pa_stream *stream;
-
-            flags = PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
-                    PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE;
-
-            spec.format = PA_SAMPLE_S16NE;
-            spec.rate = 44100;
-            spec.channels = 2;
-
-            stream = ALCpulsePlayback_connectStream(NULL, loop, context, flags,
-                                                    NULL, &spec, NULL);
-            if(stream)
-            {
-                o = pa_context_get_sink_info_by_name(context, pa_stream_get_device_name(stream),
-                                                     ALCpulsePlayback_deviceCallback, loop);
-                wait_for_operation(o, loop);
-
-                pa_stream_disconnect(stream);
-                pa_stream_unref(stream);
-                stream = NULL;
-            }
-
-            o = pa_context_get_sink_info_list(context, ALCpulsePlayback_deviceCallback, loop);
-            wait_for_operation(o, loop);
-
-            pa_context_disconnect(context);
-            pa_context_unref(context);
-        }
-        pa_threaded_mainloop_unlock(loop);
-        pa_threaded_mainloop_stop(loop);
-    }
-    if(loop)
-        pa_threaded_mainloop_free(loop);
-}
-
-
-static void ALCpulsePlayback_bufferAttrCallback(pa_stream *stream, void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-
-    self->attr = *pa_stream_get_buffer_attr(stream);
-    TRACE("minreq=%d, tlength=%d, prebuf=%d\n", self->attr.minreq, self->attr.tlength, self->attr.prebuf);
-    /* FIXME: Update the device's UpdateSize (and/or NumUpdates) using the new
-     * buffer attributes? Changing UpdateSize will change the ALC_REFRESH
-     * property, which probably shouldn't change between device resets. But
-     * leaving it alone means ALC_REFRESH will be off.
-     */
-}
-
-static void ALCpulsePlayback_contextStateCallback(pa_context *context, void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-    if(pa_context_get_state(context) == PA_CONTEXT_FAILED)
-    {
-        ERR("Received context failure!\n");
-        aluHandleDisconnect(STATIC_CAST(ALCbackend,self)->mDevice, "Playback state failure");
-    }
-    pa_threaded_mainloop_signal(self->loop, 0);
-}
-
-static void ALCpulsePlayback_streamStateCallback(pa_stream *stream, void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-    if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
-    {
-        ERR("Received stream failure!\n");
-        aluHandleDisconnect(STATIC_CAST(ALCbackend,self)->mDevice, "Playback stream failure");
-    }
-    pa_threaded_mainloop_signal(self->loop, 0);
-}
-
-static void ALCpulsePlayback_streamWriteCallback(pa_stream* UNUSED(p), size_t UNUSED(nbytes), void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-    pa_threaded_mainloop_signal(self->loop, 0);
-}
-
-static void ALCpulsePlayback_sinkInfoCallback(pa_context *UNUSED(context), const pa_sink_info *info, int eol, void *pdata)
-{
-    static const struct {
-        enum DevFmtChannels chans;
-        pa_channel_map map;
-    } chanmaps[] = {
-        { DevFmtX71, { 8, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
-            PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
-            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
-            PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
-        } } },
-        { DevFmtX61, { 7, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
-            PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
-            PA_CHANNEL_POSITION_REAR_CENTER,
-            PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
-        } } },
-        { DevFmtX51, { 6, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
-            PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
-            PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
-        } } },
-        { DevFmtX51Rear, { 6, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
-            PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
-            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT
-        } } },
-        { DevFmtQuad, { 4, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
-            PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT
-        } } },
-        { DevFmtStereo, { 2, {
-            PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT
-        } } },
-        { DevFmtMono, { 1, {PA_CHANNEL_POSITION_MONO} } }
-    };
-    ALCpulsePlayback *self = pdata;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    size_t i;
-
-    if(eol)
-    {
-        pa_threaded_mainloop_signal(self->loop, 0);
-        return;
-    }
-
-    for(i = 0;i < COUNTOF(chanmaps);i++)
-    {
-        if(pa_channel_map_superset(&info->channel_map, &chanmaps[i].map))
-        {
-            if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
-                device->FmtChans = chanmaps[i].chans;
-            break;
-        }
-    }
-    if(i == COUNTOF(chanmaps))
-    {
-        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);
-    }
-
-    if(info->active_port)
-        TRACE("Active port: %s (%s)\n", info->active_port->name, info->active_port->description);
-    device->IsHeadphones = (info->active_port &&
-                            strcmp(info->active_port->name, "analog-output-headphones") == 0 &&
-                            device->FmtChans == DevFmtStereo);
-}
-
-static void ALCpulsePlayback_sinkNameCallback(pa_context *UNUSED(context), const pa_sink_info *info, int eol, void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-
-    if(eol)
-    {
-        pa_threaded_mainloop_signal(self->loop, 0);
-        return;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, info->description);
-}
-
-
-static void ALCpulsePlayback_streamMovedCallback(pa_stream *stream, void *pdata)
-{
-    ALCpulsePlayback *self = pdata;
-
-    alstr_copy_cstr(&self->device_name, pa_stream_get_device_name(stream));
-
-    TRACE("Stream moved to %s\n", alstr_get_cstr(self->device_name));
-}
-
-
-static pa_stream *ALCpulsePlayback_connectStream(const char *device_name,
-    pa_threaded_mainloop *loop, pa_context *context,
-    pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec,
-    pa_channel_map *chanmap)
-{
-    pa_stream_state_t state;
-    pa_stream *stream;
-
-    if(!device_name)
-    {
-        device_name = getenv("ALSOFT_PULSE_DEFAULT");
-        if(device_name && !device_name[0])
-            device_name = NULL;
-    }
-
-    stream = pa_stream_new_with_proplist(context, "Playback Stream", spec, chanmap, prop_filter);
-    if(!stream)
-    {
-        ERR("pa_stream_new_with_proplist() failed: %s\n", pa_strerror(pa_context_errno(context)));
-        return NULL;
-    }
-
-    pa_stream_set_state_callback(stream, stream_state_callback, loop);
-
-    if(pa_stream_connect_playback(stream, device_name, attr, flags, NULL, NULL) < 0)
-    {
-        ERR("Stream did not connect: %s\n", pa_strerror(pa_context_errno(context)));
-        pa_stream_unref(stream);
-        return NULL;
-    }
-
-    while((state=pa_stream_get_state(stream)) != PA_STREAM_READY)
-    {
-        if(!PA_STREAM_IS_GOOD(state))
-        {
-            ERR("Stream did not get ready: %s\n", pa_strerror(pa_context_errno(context)));
-            pa_stream_unref(stream);
-            return NULL;
-        }
-
-        pa_threaded_mainloop_wait(loop);
-    }
-    pa_stream_set_state_callback(stream, NULL, NULL);
-
-    return stream;
-}
-
-
-static int ALCpulsePlayback_mixerProc(void *ptr)
-{
-    ALCpulsePlayback *self = ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    ALuint buffer_size;
-    size_t frame_size;
-    ssize_t len;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    pa_threaded_mainloop_lock(self->loop);
-    frame_size = pa_frame_size(&self->spec);
-
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        void *buf;
-        int ret;
-
-        len = pa_stream_writable_size(self->stream);
-        if(len < 0)
-        {
-            ERR("Failed to get writable size: %ld", (long)len);
-            aluHandleDisconnect(device, "Failed to get writable size: %ld", (long)len);
-            break;
-        }
-
-        /* Make sure we're going to write at least 2 'periods' (minreqs), in
-         * case the server increased it since starting playback. Also round up
-         * the number of writable periods if it's not an integer count.
-         */
-        buffer_size = maxu((self->attr.tlength + self->attr.minreq/2) / self->attr.minreq, 2) *
-                      self->attr.minreq;
-
-        /* NOTE: This assumes pa_stream_writable_size returns between 0 and
-         * tlength, else there will be more latency than intended.
-         */
-        len = mini(len - (ssize_t)self->attr.tlength, 0) + buffer_size;
-        if(len < (int32_t)self->attr.minreq)
-        {
-            if(pa_stream_is_corked(self->stream))
-            {
-                pa_operation *o;
-                o = pa_stream_cork(self->stream, 0, NULL, NULL);
-                if(o) pa_operation_unref(o);
-            }
-            pa_threaded_mainloop_wait(self->loop);
-            continue;
-        }
-
-        len -= len%self->attr.minreq;
-        len -= len%frame_size;
-
-        buf = pa_xmalloc(len);
-
-        aluMixData(device, buf, len/frame_size);
-
-        ret = pa_stream_write(self->stream, buf, len, pa_xfree, 0, PA_SEEK_RELATIVE);
-        if(ret != PA_OK) ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret));
-    }
-    pa_threaded_mainloop_unlock(self->loop);
-
-    return 0;
-}
-
-
-static ALCenum ALCpulsePlayback_open(ALCpulsePlayback *self, const ALCchar *name)
-{
-    const_al_string dev_name = AL_STRING_INIT_STATIC();
-    const char *pulse_name = NULL;
-    pa_stream_flags_t flags;
-    pa_sample_spec spec;
-
-    if(name)
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(PlaybackDevices) == 0)
-            ALCpulsePlayback_probeDevices();
-
-#define MATCH_NAME(iter) (alstr_cmp_cstr((iter)->name, name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(PlaybackDevices))
-            return ALC_INVALID_VALUE;
-        pulse_name = alstr_get_cstr(iter->device_name);
-        dev_name = iter->name;
-    }
-
-    if(!pulse_open(&self->loop, &self->context, ALCpulsePlayback_contextStateCallback, self))
-        return ALC_INVALID_VALUE;
-
-    pa_threaded_mainloop_lock(self->loop);
-
-    flags = PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
-            PA_STREAM_FIX_CHANNELS;
-    if(!GetConfigValueBool(NULL, "pulse", "allow-moves", 0))
-        flags |= PA_STREAM_DONT_MOVE;
-
-    spec.format = PA_SAMPLE_S16NE;
-    spec.rate = 44100;
-    spec.channels = 2;
-
-    TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
-    self->stream = ALCpulsePlayback_connectStream(pulse_name, self->loop, self->context,
-                                                  flags, NULL, &spec, NULL);
-    if(!self->stream)
-    {
-        pa_threaded_mainloop_unlock(self->loop);
-        pulse_close(self->loop, self->context, self->stream);
-        self->loop = NULL;
-        self->context = NULL;
-        return ALC_INVALID_VALUE;
-    }
-    pa_stream_set_moved_callback(self->stream, ALCpulsePlayback_streamMovedCallback, self);
-
-    alstr_copy_cstr(&self->device_name, pa_stream_get_device_name(self->stream));
-    if(alstr_empty(dev_name))
-    {
-        pa_operation *o = pa_context_get_sink_info_by_name(
-            self->context, alstr_get_cstr(self->device_name),
-            ALCpulsePlayback_sinkNameCallback, self
-        );
-        wait_for_operation(o, self->loop);
-    }
-    else
-    {
-        ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-        alstr_copy(&device->DeviceName, dev_name);
-    }
-
-    pa_threaded_mainloop_unlock(self->loop);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCpulsePlayback_reset(ALCpulsePlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    pa_stream_flags_t flags = 0;
-    const char *mapname = NULL;
-    pa_channel_map chanmap;
-    pa_operation *o;
-
-    pa_threaded_mainloop_lock(self->loop);
-
-    if(self->stream)
-    {
-        pa_stream_set_state_callback(self->stream, NULL, NULL);
-        pa_stream_set_moved_callback(self->stream, NULL, NULL);
-        pa_stream_set_write_callback(self->stream, NULL, NULL);
-        pa_stream_set_buffer_attr_callback(self->stream, NULL, NULL);
-        pa_stream_disconnect(self->stream);
-        pa_stream_unref(self->stream);
-        self->stream = NULL;
-    }
-
-    o = pa_context_get_sink_info_by_name(self->context, alstr_get_cstr(self->device_name),
-                                         ALCpulsePlayback_sinkInfoCallback, self);
-    wait_for_operation(o, self->loop);
-
-    if(GetConfigValueBool(alstr_get_cstr(device->DeviceName), "pulse", "fix-rate", 0) ||
-       !(device->Flags&DEVICE_FREQUENCY_REQUEST))
-        flags |= PA_STREAM_FIX_RATE;
-    flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
-    flags |= PA_STREAM_ADJUST_LATENCY;
-    flags |= PA_STREAM_START_CORKED;
-    if(!GetConfigValueBool(NULL, "pulse", "allow-moves", 0))
-        flags |= PA_STREAM_DONT_MOVE;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            device->FmtType = DevFmtUByte;
-            /* fall-through */
-        case DevFmtUByte:
-            self->spec.format = PA_SAMPLE_U8;
-            break;
-        case DevFmtUShort:
-            device->FmtType = DevFmtShort;
-            /* fall-through */
-        case DevFmtShort:
-            self->spec.format = PA_SAMPLE_S16NE;
-            break;
-        case DevFmtUInt:
-            device->FmtType = DevFmtInt;
-            /* fall-through */
-        case DevFmtInt:
-            self->spec.format = PA_SAMPLE_S32NE;
-            break;
-        case DevFmtFloat:
-            self->spec.format = PA_SAMPLE_FLOAT32NE;
-            break;
-    }
-    self->spec.rate = device->Frequency;
-    self->spec.channels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
-    if(pa_sample_spec_valid(&self->spec) == 0)
-    {
-        ERR("Invalid sample format\n");
-        pa_threaded_mainloop_unlock(self->loop);
-        return ALC_FALSE;
-    }
-
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            mapname = "mono";
-            break;
-        case DevFmtAmbi3D:
-            device->FmtChans = DevFmtStereo;
-            /*fall-through*/
-        case DevFmtStereo:
-            mapname = "front-left,front-right";
-            break;
-        case DevFmtQuad:
-            mapname = "front-left,front-right,rear-left,rear-right";
-            break;
-        case DevFmtX51:
-            mapname = "front-left,front-right,front-center,lfe,side-left,side-right";
-            break;
-        case DevFmtX51Rear:
-            mapname = "front-left,front-right,front-center,lfe,rear-left,rear-right";
-            break;
-        case DevFmtX61:
-            mapname = "front-left,front-right,front-center,lfe,rear-center,side-left,side-right";
-            break;
-        case DevFmtX71:
-            mapname = "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right";
-            break;
-    }
-    if(!pa_channel_map_parse(&chanmap, mapname))
-    {
-        ERR("Failed to build channel map for %s\n", DevFmtChannelsString(device->FmtChans));
-        pa_threaded_mainloop_unlock(self->loop);
-        return ALC_FALSE;
-    }
-    SetDefaultWFXChannelOrder(device);
-
-    self->attr.fragsize = -1;
-    self->attr.prebuf = 0;
-    self->attr.minreq = device->UpdateSize * pa_frame_size(&self->spec);
-    self->attr.tlength = self->attr.minreq * maxu(device->NumUpdates, 2);
-    self->attr.maxlength = -1;
-
-    self->stream = ALCpulsePlayback_connectStream(alstr_get_cstr(self->device_name),
-        self->loop, self->context, flags, &self->attr, &self->spec, &chanmap
-    );
-    if(!self->stream)
-    {
-        pa_threaded_mainloop_unlock(self->loop);
-        return ALC_FALSE;
-    }
-    pa_stream_set_state_callback(self->stream, ALCpulsePlayback_streamStateCallback, self);
-    pa_stream_set_moved_callback(self->stream, ALCpulsePlayback_streamMovedCallback, self);
-    pa_stream_set_write_callback(self->stream, ALCpulsePlayback_streamWriteCallback, self);
-
-    self->spec = *(pa_stream_get_sample_spec(self->stream));
-    if(device->Frequency != self->spec.rate)
-    {
-        /* Server updated our playback rate, so modify the buffer attribs
-         * accordingly. */
-        device->NumUpdates = (ALuint)clampd(
-            (ALdouble)device->NumUpdates/device->Frequency*self->spec.rate + 0.5, 2.0, 16.0
-        );
-
-        self->attr.minreq  = device->UpdateSize * pa_frame_size(&self->spec);
-        self->attr.tlength = self->attr.minreq * device->NumUpdates;
-        self->attr.maxlength = -1;
-        self->attr.prebuf  = 0;
-
-        o = pa_stream_set_buffer_attr(self->stream, &self->attr,
-                                      stream_success_callback, self->loop);
-        wait_for_operation(o, self->loop);
-
-        device->Frequency = self->spec.rate;
-    }
-
-    pa_stream_set_buffer_attr_callback(self->stream, ALCpulsePlayback_bufferAttrCallback, self);
-    ALCpulsePlayback_bufferAttrCallback(self->stream, self);
-
-    device->NumUpdates = (ALuint)clampu64(
-        (self->attr.tlength + self->attr.minreq/2) / self->attr.minreq, 2, 16
-    );
-    device->UpdateSize = self->attr.minreq / pa_frame_size(&self->spec);
-
-    /* HACK: prebuf should be 0 as that's what we set it to. However on some
-     * systems it comes back as non-0, so we have to make sure the device will
-     * write enough audio to start playback. The lack of manual start control
-     * may have unintended consequences, but it's better than not starting at
-     * all.
-     */
-    if(self->attr.prebuf != 0)
-    {
-        ALuint len = self->attr.prebuf / pa_frame_size(&self->spec);
-        if(len <= device->UpdateSize*device->NumUpdates)
-            ERR("Non-0 prebuf, %u samples (%u bytes), device has %u samples\n",
-                len, self->attr.prebuf, device->UpdateSize*device->NumUpdates);
-        else
-        {
-            ERR("Large prebuf, %u samples (%u bytes), increasing device from %u samples",
-                len, self->attr.prebuf, device->UpdateSize*device->NumUpdates);
-            device->NumUpdates = (len+device->UpdateSize-1) / device->UpdateSize;
-        }
-    }
-
-    pa_threaded_mainloop_unlock(self->loop);
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCpulsePlayback_start(ALCpulsePlayback *self)
-{
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCpulsePlayback_mixerProc, self) != althrd_success)
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCpulsePlayback_stop(ALCpulsePlayback *self)
-{
-    pa_operation *o;
-    int res;
-
-    if(!self->stream || ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-
-    /* Signal the main loop in case PulseAudio isn't sending us audio requests
-     * (e.g. if the device is suspended). We need to lock the mainloop in case
-     * the mixer is between checking the killNow flag but before waiting for
-     * the signal.
-     */
-    pa_threaded_mainloop_lock(self->loop);
-    pa_threaded_mainloop_unlock(self->loop);
-    pa_threaded_mainloop_signal(self->loop, 0);
-    althrd_join(self->thread, &res);
-
-    pa_threaded_mainloop_lock(self->loop);
-
-    o = pa_stream_cork(self->stream, 1, stream_success_callback, self->loop);
-    wait_for_operation(o, self->loop);
-
-    pa_threaded_mainloop_unlock(self->loop);
-}
-
-
-static ClockLatency ALCpulsePlayback_getClockLatency(ALCpulsePlayback *self)
-{
-    ClockLatency ret;
-    pa_usec_t latency;
-    int neg, err;
-
-    pa_threaded_mainloop_lock(self->loop);
-    ret.ClockTime = GetDeviceClockTime(STATIC_CAST(ALCbackend,self)->mDevice);
-    err = pa_stream_get_latency(self->stream, &latency, &neg);
-    pa_threaded_mainloop_unlock(self->loop);
-
-    if(UNLIKELY(err != 0))
-    {
-        /* FIXME: if err = -PA_ERR_NODATA, it means we were called too soon
-         * after starting the stream and no timing info has been received from
-         * the server yet. Should we wait, possibly stalling the app, or give a
-         * dummy value? Either way, it shouldn't be 0. */
-        if(err != -PA_ERR_NODATA)
-            ERR("Failed to get stream latency: 0x%x\n", err);
-        latency = 0;
-        neg = 0;
-    }
-    else if(UNLIKELY(neg))
-        latency = 0;
-    ret.Latency = (ALint64)minu64(latency, U64(0x7fffffffffffffff)/1000) * 1000;
-
-    return ret;
-}
-
-
-static void ALCpulsePlayback_lock(ALCpulsePlayback *self)
-{
-    pa_threaded_mainloop_lock(self->loop);
-}
-
-static void ALCpulsePlayback_unlock(ALCpulsePlayback *self)
-{
-    pa_threaded_mainloop_unlock(self->loop);
-}
-
-
-typedef struct ALCpulseCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    al_string device_name;
-
-    const void *cap_store;
-    size_t cap_len;
-    size_t cap_remain;
-
-    ALCuint last_readable;
-
-    pa_buffer_attr attr;
-    pa_sample_spec spec;
-
-    pa_threaded_mainloop *loop;
-
-    pa_stream *stream;
-    pa_context *context;
-} ALCpulseCapture;
-
-static void ALCpulseCapture_deviceCallback(pa_context *context, const pa_source_info *info, int eol, void *pdata);
-static void ALCpulseCapture_probeDevices(void);
-
-static void ALCpulseCapture_contextStateCallback(pa_context *context, void *pdata);
-static void ALCpulseCapture_streamStateCallback(pa_stream *stream, void *pdata);
-static void ALCpulseCapture_sourceNameCallback(pa_context *context, const pa_source_info *info, int eol, void *pdata);
-static void ALCpulseCapture_streamMovedCallback(pa_stream *stream, void *pdata);
-static pa_stream *ALCpulseCapture_connectStream(const char *device_name,
-                                                pa_threaded_mainloop *loop, pa_context *context,
-                                                pa_stream_flags_t flags, pa_buffer_attr *attr,
-                                                pa_sample_spec *spec, pa_channel_map *chanmap);
-
-static void ALCpulseCapture_Construct(ALCpulseCapture *self, ALCdevice *device);
-static void ALCpulseCapture_Destruct(ALCpulseCapture *self);
-static ALCenum ALCpulseCapture_open(ALCpulseCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCpulseCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCpulseCapture_start(ALCpulseCapture *self);
-static void ALCpulseCapture_stop(ALCpulseCapture *self);
-static ALCenum ALCpulseCapture_captureSamples(ALCpulseCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCpulseCapture_availableSamples(ALCpulseCapture *self);
-static ClockLatency ALCpulseCapture_getClockLatency(ALCpulseCapture *self);
-static void ALCpulseCapture_lock(ALCpulseCapture *self);
-static void ALCpulseCapture_unlock(ALCpulseCapture *self);
-DECLARE_DEFAULT_ALLOCATORS(ALCpulseCapture)
-
-DEFINE_ALCBACKEND_VTABLE(ALCpulseCapture);
-
-
-static void ALCpulseCapture_Construct(ALCpulseCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCpulseCapture, ALCbackend, self);
-
-    self->loop = NULL;
-    AL_STRING_INIT(self->device_name);
-}
-
-static void ALCpulseCapture_Destruct(ALCpulseCapture *self)
-{
-    if(self->loop)
-    {
-        pulse_close(self->loop, self->context, self->stream);
-        self->loop = NULL;
-        self->context = NULL;
-        self->stream = NULL;
-    }
-    AL_STRING_DEINIT(self->device_name);
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static void ALCpulseCapture_deviceCallback(pa_context *UNUSED(context), const pa_source_info *info, int eol, void *pdata)
-{
-    pa_threaded_mainloop *loop = pdata;
-    const DevMap *iter;
-    DevMap entry;
-    int count;
-
-    if(eol)
-    {
-        pa_threaded_mainloop_signal(loop, 0);
-        return;
-    }
-
-#define MATCH_INFO_NAME(iter) (alstr_cmp_cstr((iter)->device_name, info->name) == 0)
-    VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_INFO_NAME);
-    if(iter != VECTOR_END(CaptureDevices)) return;
-#undef MATCH_INFO_NAME
-
-    AL_STRING_INIT(entry.name);
-    AL_STRING_INIT(entry.device_name);
-
-    alstr_copy_cstr(&entry.device_name, info->name);
-
-    count = 0;
-    while(1)
-    {
-        alstr_copy_cstr(&entry.name, info->description);
-        if(count != 0)
-        {
-            char str[64];
-            snprintf(str, sizeof(str), " #%d", count+1);
-            alstr_append_cstr(&entry.name, str);
-        }
-
-#define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_ENTRY);
-        if(iter == VECTOR_END(CaptureDevices)) break;
-#undef MATCH_ENTRY
-        count++;
-    }
-
-    TRACE("Got device \"%s\", \"%s\"\n", alstr_get_cstr(entry.name), alstr_get_cstr(entry.device_name));
-
-    VECTOR_PUSH_BACK(CaptureDevices, entry);
-}
-
-static void ALCpulseCapture_probeDevices(void)
-{
-    pa_threaded_mainloop *loop;
-
-    clear_devlist(&CaptureDevices);
-
-    if((loop=pa_threaded_mainloop_new()) &&
-       pa_threaded_mainloop_start(loop) >= 0)
-    {
-        pa_context *context;
-
-        pa_threaded_mainloop_lock(loop);
-        context = connect_context(loop, AL_FALSE);
-        if(context)
-        {
-            pa_operation *o;
-            pa_stream_flags_t flags;
-            pa_sample_spec spec;
-            pa_stream *stream;
-
-            flags = PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
-                    PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE;
-
-            spec.format = PA_SAMPLE_S16NE;
-            spec.rate = 44100;
-            spec.channels = 1;
-
-            stream = ALCpulseCapture_connectStream(NULL, loop, context, flags,
-                                                   NULL, &spec, NULL);
-            if(stream)
-            {
-                o = pa_context_get_source_info_by_name(context, pa_stream_get_device_name(stream),
-                                                       ALCpulseCapture_deviceCallback, loop);
-                wait_for_operation(o, loop);
-
-                pa_stream_disconnect(stream);
-                pa_stream_unref(stream);
-                stream = NULL;
-            }
-
-            o = pa_context_get_source_info_list(context, ALCpulseCapture_deviceCallback, loop);
-            wait_for_operation(o, loop);
-
-            pa_context_disconnect(context);
-            pa_context_unref(context);
-        }
-        pa_threaded_mainloop_unlock(loop);
-        pa_threaded_mainloop_stop(loop);
-    }
-    if(loop)
-        pa_threaded_mainloop_free(loop);
-}
-
-
-static void ALCpulseCapture_contextStateCallback(pa_context *context, void *pdata)
-{
-    ALCpulseCapture *self = pdata;
-    if(pa_context_get_state(context) == PA_CONTEXT_FAILED)
-    {
-        ERR("Received context failure!\n");
-        aluHandleDisconnect(STATIC_CAST(ALCbackend,self)->mDevice, "Capture state failure");
-    }
-    pa_threaded_mainloop_signal(self->loop, 0);
-}
-
-static void ALCpulseCapture_streamStateCallback(pa_stream *stream, void *pdata)
-{
-    ALCpulseCapture *self = pdata;
-    if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
-    {
-        ERR("Received stream failure!\n");
-        aluHandleDisconnect(STATIC_CAST(ALCbackend,self)->mDevice, "Capture stream failure");
-    }
-    pa_threaded_mainloop_signal(self->loop, 0);
-}
-
-
-static void ALCpulseCapture_sourceNameCallback(pa_context *UNUSED(context), const pa_source_info *info, int eol, void *pdata)
-{
-    ALCpulseCapture *self = pdata;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-
-    if(eol)
-    {
-        pa_threaded_mainloop_signal(self->loop, 0);
-        return;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, info->description);
-}
-
-
-static void ALCpulseCapture_streamMovedCallback(pa_stream *stream, void *pdata)
-{
-    ALCpulseCapture *self = pdata;
-
-    alstr_copy_cstr(&self->device_name, pa_stream_get_device_name(stream));
-
-    TRACE("Stream moved to %s\n", alstr_get_cstr(self->device_name));
-}
-
-
-static pa_stream *ALCpulseCapture_connectStream(const char *device_name,
-    pa_threaded_mainloop *loop, pa_context *context,
-    pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec,
-    pa_channel_map *chanmap)
-{
-    pa_stream_state_t state;
-    pa_stream *stream;
-
-    stream = pa_stream_new_with_proplist(context, "Capture Stream", spec, chanmap, prop_filter);
-    if(!stream)
-    {
-        ERR("pa_stream_new_with_proplist() failed: %s\n", pa_strerror(pa_context_errno(context)));
-        return NULL;
-    }
-
-    pa_stream_set_state_callback(stream, stream_state_callback, loop);
-
-    if(pa_stream_connect_record(stream, device_name, attr, flags) < 0)
-    {
-        ERR("Stream did not connect: %s\n", pa_strerror(pa_context_errno(context)));
-        pa_stream_unref(stream);
-        return NULL;
-    }
-
-    while((state=pa_stream_get_state(stream)) != PA_STREAM_READY)
-    {
-        if(!PA_STREAM_IS_GOOD(state))
-        {
-            ERR("Stream did not get ready: %s\n", pa_strerror(pa_context_errno(context)));
-            pa_stream_unref(stream);
-            return NULL;
-        }
-
-        pa_threaded_mainloop_wait(loop);
-    }
-    pa_stream_set_state_callback(stream, NULL, NULL);
-
-    return stream;
-}
-
-
-static ALCenum ALCpulseCapture_open(ALCpulseCapture *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    const char *pulse_name = NULL;
-    pa_stream_flags_t flags = 0;
-    const char *mapname = NULL;
-    pa_channel_map chanmap;
-    ALuint samples;
-
-    if(name)
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(CaptureDevices) == 0)
-            ALCpulseCapture_probeDevices();
-
-#define MATCH_NAME(iter) (alstr_cmp_cstr((iter)->name, name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
-#undef MATCH_NAME
-        if(iter == VECTOR_END(CaptureDevices))
-            return ALC_INVALID_VALUE;
-        pulse_name = alstr_get_cstr(iter->device_name);
-        alstr_copy(&device->DeviceName, iter->name);
-    }
-
-    if(!pulse_open(&self->loop, &self->context, ALCpulseCapture_contextStateCallback, self))
-        return ALC_INVALID_VALUE;
-
-    pa_threaded_mainloop_lock(self->loop);
-
-    switch(device->FmtType)
-    {
-        case DevFmtUByte:
-            self->spec.format = PA_SAMPLE_U8;
-            break;
-        case DevFmtShort:
-            self->spec.format = PA_SAMPLE_S16NE;
-            break;
-        case DevFmtInt:
-            self->spec.format = PA_SAMPLE_S32NE;
-            break;
-        case DevFmtFloat:
-            self->spec.format = PA_SAMPLE_FLOAT32NE;
-            break;
-        case DevFmtByte:
-        case DevFmtUShort:
-        case DevFmtUInt:
-            ERR("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
-            pa_threaded_mainloop_unlock(self->loop);
-            goto fail;
-    }
-
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            mapname = "mono";
-            break;
-        case DevFmtStereo:
-            mapname = "front-left,front-right";
-            break;
-        case DevFmtQuad:
-            mapname = "front-left,front-right,rear-left,rear-right";
-            break;
-        case DevFmtX51:
-            mapname = "front-left,front-right,front-center,lfe,side-left,side-right";
-            break;
-        case DevFmtX51Rear:
-            mapname = "front-left,front-right,front-center,lfe,rear-left,rear-right";
-            break;
-        case DevFmtX61:
-            mapname = "front-left,front-right,front-center,lfe,rear-center,side-left,side-right";
-            break;
-        case DevFmtX71:
-            mapname = "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right";
-            break;
-        case DevFmtAmbi3D:
-            ERR("%s capture samples not supported\n", DevFmtChannelsString(device->FmtChans));
-            pa_threaded_mainloop_unlock(self->loop);
-            goto fail;
-    }
-    if(!pa_channel_map_parse(&chanmap, mapname))
-    {
-        ERR("Failed to build channel map for %s\n", DevFmtChannelsString(device->FmtChans));
-        pa_threaded_mainloop_unlock(self->loop);
-        return ALC_FALSE;
-    }
-
-    self->spec.rate = device->Frequency;
-    self->spec.channels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
-    if(pa_sample_spec_valid(&self->spec) == 0)
-    {
-        ERR("Invalid sample format\n");
-        pa_threaded_mainloop_unlock(self->loop);
-        goto fail;
-    }
-
-    if(!pa_channel_map_init_auto(&chanmap, self->spec.channels, PA_CHANNEL_MAP_WAVEEX))
-    {
-        ERR("Couldn't build map for channel count (%d)!\n", self->spec.channels);
-        pa_threaded_mainloop_unlock(self->loop);
-        goto fail;
-    }
-
-    samples = device->UpdateSize * device->NumUpdates;
-    samples = maxu(samples, 100 * device->Frequency / 1000);
-
-    self->attr.minreq = -1;
-    self->attr.prebuf = -1;
-    self->attr.maxlength = samples * pa_frame_size(&self->spec);
-    self->attr.tlength = -1;
-    self->attr.fragsize = minu(samples, 50*device->Frequency/1000) *
-                          pa_frame_size(&self->spec);
-
-    flags |= PA_STREAM_START_CORKED|PA_STREAM_ADJUST_LATENCY;
-    if(!GetConfigValueBool(NULL, "pulse", "allow-moves", 0))
-        flags |= PA_STREAM_DONT_MOVE;
-
-    TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
-    self->stream = ALCpulseCapture_connectStream(pulse_name,
-        self->loop, self->context, flags, &self->attr, &self->spec, &chanmap
-    );
-    if(!self->stream)
-    {
-        pa_threaded_mainloop_unlock(self->loop);
-        goto fail;
-    }
-    pa_stream_set_moved_callback(self->stream, ALCpulseCapture_streamMovedCallback, self);
-    pa_stream_set_state_callback(self->stream, ALCpulseCapture_streamStateCallback, self);
-
-    alstr_copy_cstr(&self->device_name, pa_stream_get_device_name(self->stream));
-    if(alstr_empty(device->DeviceName))
-    {
-        pa_operation *o = pa_context_get_source_info_by_name(
-            self->context, alstr_get_cstr(self->device_name),
-            ALCpulseCapture_sourceNameCallback, self
-        );
-        wait_for_operation(o, self->loop);
-    }
-
-    pa_threaded_mainloop_unlock(self->loop);
-    return ALC_NO_ERROR;
-
-fail:
-    pulse_close(self->loop, self->context, self->stream);
-    self->loop = NULL;
-    self->context = NULL;
-    self->stream = NULL;
-
-    return ALC_INVALID_VALUE;
-}
-
-static ALCboolean ALCpulseCapture_start(ALCpulseCapture *self)
-{
-    pa_operation *o;
-    pa_threaded_mainloop_lock(self->loop);
-    o = pa_stream_cork(self->stream, 0, stream_success_callback, self->loop);
-    wait_for_operation(o, self->loop);
-    pa_threaded_mainloop_unlock(self->loop);
-    return ALC_TRUE;
-}
-
-static void ALCpulseCapture_stop(ALCpulseCapture *self)
-{
-    pa_operation *o;
-    pa_threaded_mainloop_lock(self->loop);
-    o = pa_stream_cork(self->stream, 1, stream_success_callback, self->loop);
-    wait_for_operation(o, self->loop);
-    pa_threaded_mainloop_unlock(self->loop);
-}
-
-static ALCenum ALCpulseCapture_captureSamples(ALCpulseCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    ALCuint todo = samples * pa_frame_size(&self->spec);
-
-    /* Capture is done in fragment-sized chunks, so we loop until we get all
-     * that's available */
-    self->last_readable -= todo;
-    pa_threaded_mainloop_lock(self->loop);
-    while(todo > 0)
-    {
-        size_t rem = todo;
-
-        if(self->cap_len == 0)
-        {
-            pa_stream_state_t state;
-
-            state = pa_stream_get_state(self->stream);
-            if(!PA_STREAM_IS_GOOD(state))
-            {
-                aluHandleDisconnect(device, "Bad capture state: %u", state);
-                break;
-            }
-            if(pa_stream_peek(self->stream, &self->cap_store, &self->cap_len) < 0)
-            {
-                ERR("pa_stream_peek() failed: %s\n",
-                    pa_strerror(pa_context_errno(self->context)));
-                aluHandleDisconnect(device, "Failed retrieving capture samples: %s",
-                                    pa_strerror(pa_context_errno(self->context)));
-                break;
-            }
-            self->cap_remain = self->cap_len;
-        }
-        if(rem > self->cap_remain)
-            rem = self->cap_remain;
-
-        memcpy(buffer, self->cap_store, rem);
-
-        buffer = (ALbyte*)buffer + rem;
-        todo -= rem;
-
-        self->cap_store = (ALbyte*)self->cap_store + rem;
-        self->cap_remain -= rem;
-        if(self->cap_remain == 0)
-        {
-            pa_stream_drop(self->stream);
-            self->cap_len = 0;
-        }
-    }
-    pa_threaded_mainloop_unlock(self->loop);
-    if(todo > 0)
-        memset(buffer, ((device->FmtType==DevFmtUByte) ? 0x80 : 0), todo);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCpulseCapture_availableSamples(ALCpulseCapture *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    size_t readable = self->cap_remain;
-
-    if(ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        ssize_t got;
-        pa_threaded_mainloop_lock(self->loop);
-        got = pa_stream_readable_size(self->stream);
-        if(got < 0)
-        {
-            ERR("pa_stream_readable_size() failed: %s\n", pa_strerror(got));
-            aluHandleDisconnect(device, "Failed getting readable size: %s", pa_strerror(got));
-        }
-        else if((size_t)got > self->cap_len)
-            readable += got - self->cap_len;
-        pa_threaded_mainloop_unlock(self->loop);
-    }
-
-    if(self->last_readable < readable)
-        self->last_readable = readable;
-    return self->last_readable / pa_frame_size(&self->spec);
-}
-
-
-static ClockLatency ALCpulseCapture_getClockLatency(ALCpulseCapture *self)
-{
-    ClockLatency ret;
-    pa_usec_t latency;
-    int neg, err;
-
-    pa_threaded_mainloop_lock(self->loop);
-    ret.ClockTime = GetDeviceClockTime(STATIC_CAST(ALCbackend,self)->mDevice);
-    err = pa_stream_get_latency(self->stream, &latency, &neg);
-    pa_threaded_mainloop_unlock(self->loop);
-
-    if(UNLIKELY(err != 0))
-    {
-        ERR("Failed to get stream latency: 0x%x\n", err);
-        latency = 0;
-        neg = 0;
-    }
-    else if(UNLIKELY(neg))
-        latency = 0;
-    ret.Latency = (ALint64)minu64(latency, U64(0x7fffffffffffffff)/1000) * 1000;
-
-    return ret;
-}
-
-
-static void ALCpulseCapture_lock(ALCpulseCapture *self)
-{
-    pa_threaded_mainloop_lock(self->loop);
-}
-
-static void ALCpulseCapture_unlock(ALCpulseCapture *self)
-{
-    pa_threaded_mainloop_unlock(self->loop);
-}
-
-
-typedef struct ALCpulseBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCpulseBackendFactory;
-#define ALCPULSEBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCpulseBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCpulseBackendFactory_init(ALCpulseBackendFactory *self);
-static void ALCpulseBackendFactory_deinit(ALCpulseBackendFactory *self);
-static ALCboolean ALCpulseBackendFactory_querySupport(ALCpulseBackendFactory *self, ALCbackend_Type type);
-static void ALCpulseBackendFactory_probe(ALCpulseBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCpulseBackendFactory_createBackend(ALCpulseBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCpulseBackendFactory);
-
-
-static ALCboolean ALCpulseBackendFactory_init(ALCpulseBackendFactory* UNUSED(self))
-{
-    ALCboolean ret = ALC_FALSE;
-
-    VECTOR_INIT(PlaybackDevices);
-    VECTOR_INIT(CaptureDevices);
-
-    if(pulse_load())
-    {
-        pa_threaded_mainloop *loop;
-
-        pulse_ctx_flags = 0;
-        if(!GetConfigValueBool(NULL, "pulse", "spawn-server", 1))
-            pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN;
-
-        if((loop=pa_threaded_mainloop_new()) &&
-           pa_threaded_mainloop_start(loop) >= 0)
-        {
-            pa_context *context;
-
-            pa_threaded_mainloop_lock(loop);
-            context = connect_context(loop, AL_TRUE);
-            if(context)
-            {
-                ret = ALC_TRUE;
-
-                /* Some libraries (Phonon, Qt) set some pulseaudio properties
-                 * through environment variables, which causes all streams in
-                 * the process to inherit them. This attempts to filter those
-                 * properties out by setting them to 0-length data. */
-                prop_filter = pa_proplist_new();
-                pa_proplist_set(prop_filter, PA_PROP_MEDIA_ROLE, NULL, 0);
-                pa_proplist_set(prop_filter, "phonon.streamid", NULL, 0);
-
-                pa_context_disconnect(context);
-                pa_context_unref(context);
-            }
-            pa_threaded_mainloop_unlock(loop);
-            pa_threaded_mainloop_stop(loop);
-        }
-        if(loop)
-            pa_threaded_mainloop_free(loop);
-    }
-
-    return ret;
-}
-
-static void ALCpulseBackendFactory_deinit(ALCpulseBackendFactory* UNUSED(self))
-{
-    clear_devlist(&PlaybackDevices);
-    VECTOR_DEINIT(PlaybackDevices);
-
-    clear_devlist(&CaptureDevices);
-    VECTOR_DEINIT(CaptureDevices);
-
-    if(prop_filter)
-        pa_proplist_free(prop_filter);
-    prop_filter = NULL;
-
-    /* PulseAudio doesn't like being CloseLib'd sometimes */
-}
-
-static ALCboolean ALCpulseBackendFactory_querySupport(ALCpulseBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCpulseBackendFactory_probe(ALCpulseBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            ALCpulsePlayback_probeDevices();
-#define APPEND_ALL_DEVICES_LIST(e)  AppendAllDevicesList(alstr_get_cstr((e)->name))
-            VECTOR_FOR_EACH(const DevMap, PlaybackDevices, APPEND_ALL_DEVICES_LIST);
-#undef APPEND_ALL_DEVICES_LIST
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            ALCpulseCapture_probeDevices();
-#define APPEND_CAPTURE_DEVICE_LIST(e) AppendCaptureDeviceList(alstr_get_cstr((e)->name))
-            VECTOR_FOR_EACH(const DevMap, CaptureDevices, APPEND_CAPTURE_DEVICE_LIST);
-#undef APPEND_CAPTURE_DEVICE_LIST
-            break;
-    }
-}
-
-static ALCbackend* ALCpulseBackendFactory_createBackend(ALCpulseBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCpulsePlayback *backend;
-        NEW_OBJ(backend, ALCpulsePlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCpulseCapture *backend;
-        NEW_OBJ(backend, ALCpulseCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-
-#else /* PA_API_VERSION == 12 */
-
-#warning "Unsupported API version, backend will be unavailable!"
-
-typedef struct ALCpulseBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCpulseBackendFactory;
-#define ALCPULSEBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCpulseBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCpulseBackendFactory_init(ALCpulseBackendFactory* UNUSED(self))
-{
-    return ALC_FALSE;
-}
-
-static void ALCpulseBackendFactory_deinit(ALCpulseBackendFactory* UNUSED(self))
-{
-}
-
-static ALCboolean ALCpulseBackendFactory_querySupport(ALCpulseBackendFactory* UNUSED(self), ALCbackend_Type UNUSED(type))
-{
-    return ALC_FALSE;
-}
-
-static void ALCpulseBackendFactory_probe(ALCpulseBackendFactory* UNUSED(self), enum DevProbe UNUSED(type))
-{
-}
-
-static ALCbackend* ALCpulseBackendFactory_createBackend(ALCpulseBackendFactory* UNUSED(self), ALCdevice* UNUSED(device), ALCbackend_Type UNUSED(type))
-{
-    return NULL;
-}
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCpulseBackendFactory);
-
-#endif /* PA_API_VERSION == 12 */
-
-ALCbackendFactory *ALCpulseBackendFactory_getFactory(void)
-{
-    static ALCpulseBackendFactory factory = ALCPULSEBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 1521 - 0
Engine/lib/openal-soft/Alc/backends/pulseaudio.cpp

@@ -0,0 +1,1521 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Konstantinos Natsakis <[email protected]>
+ * Copyright (C) 2010 by Chris Robinson <[email protected]>
+ * 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 "backends/pulseaudio.h"
+
+#include <poll.h>
+#include <cstring>
+
+#include <array>
+#include <string>
+#include <vector>
+#include <atomic>
+#include <thread>
+#include <algorithm>
+#include <functional>
+#include <condition_variable>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "dynload.h"
+#include "strutils.h"
+
+#include <pulse/pulseaudio.h>
+
+
+namespace {
+
+#ifdef HAVE_DYNLOAD
+#define PULSE_FUNCS(MAGIC)                                                    \
+    MAGIC(pa_mainloop_new);                                                   \
+    MAGIC(pa_mainloop_free);                                                  \
+    MAGIC(pa_mainloop_set_poll_func);                                         \
+    MAGIC(pa_mainloop_run);                                                   \
+    MAGIC(pa_mainloop_quit);                                                  \
+    MAGIC(pa_mainloop_get_api);                                               \
+    MAGIC(pa_context_new);                                                    \
+    MAGIC(pa_context_unref);                                                  \
+    MAGIC(pa_context_get_state);                                              \
+    MAGIC(pa_context_disconnect);                                             \
+    MAGIC(pa_context_set_state_callback);                                     \
+    MAGIC(pa_context_errno);                                                  \
+    MAGIC(pa_context_connect);                                                \
+    MAGIC(pa_context_get_server_info);                                        \
+    MAGIC(pa_context_get_sink_info_by_name);                                  \
+    MAGIC(pa_context_get_sink_info_list);                                     \
+    MAGIC(pa_context_get_source_info_by_name);                                \
+    MAGIC(pa_context_get_source_info_list);                                   \
+    MAGIC(pa_stream_new);                                                     \
+    MAGIC(pa_stream_unref);                                                   \
+    MAGIC(pa_stream_drop);                                                    \
+    MAGIC(pa_stream_get_state);                                               \
+    MAGIC(pa_stream_peek);                                                    \
+    MAGIC(pa_stream_write);                                                   \
+    MAGIC(pa_stream_connect_record);                                          \
+    MAGIC(pa_stream_connect_playback);                                        \
+    MAGIC(pa_stream_readable_size);                                           \
+    MAGIC(pa_stream_writable_size);                                           \
+    MAGIC(pa_stream_is_corked);                                               \
+    MAGIC(pa_stream_cork);                                                    \
+    MAGIC(pa_stream_is_suspended);                                            \
+    MAGIC(pa_stream_get_device_name);                                         \
+    MAGIC(pa_stream_get_latency);                                             \
+    MAGIC(pa_stream_set_write_callback);                                      \
+    MAGIC(pa_stream_set_buffer_attr);                                         \
+    MAGIC(pa_stream_get_buffer_attr);                                         \
+    MAGIC(pa_stream_get_sample_spec);                                         \
+    MAGIC(pa_stream_get_time);                                                \
+    MAGIC(pa_stream_set_read_callback);                                       \
+    MAGIC(pa_stream_set_state_callback);                                      \
+    MAGIC(pa_stream_set_moved_callback);                                      \
+    MAGIC(pa_stream_set_underflow_callback);                                  \
+    MAGIC(pa_stream_new_with_proplist);                                       \
+    MAGIC(pa_stream_disconnect);                                              \
+    MAGIC(pa_stream_set_buffer_attr_callback);                                \
+    MAGIC(pa_stream_begin_write);                                             \
+    MAGIC(pa_channel_map_init_auto);                                          \
+    MAGIC(pa_channel_map_parse);                                              \
+    MAGIC(pa_channel_map_snprint);                                            \
+    MAGIC(pa_channel_map_equal);                                              \
+    MAGIC(pa_channel_map_superset);                                           \
+    MAGIC(pa_channel_position_to_string);                                     \
+    MAGIC(pa_operation_get_state);                                            \
+    MAGIC(pa_operation_unref);                                                \
+    MAGIC(pa_sample_spec_valid);                                              \
+    MAGIC(pa_frame_size);                                                     \
+    MAGIC(pa_strerror);                                                       \
+    MAGIC(pa_path_get_filename);                                              \
+    MAGIC(pa_get_binary_name);                                                \
+    MAGIC(pa_xmalloc);                                                        \
+    MAGIC(pa_xfree);
+
+void *pulse_handle;
+#define MAKE_FUNC(x) decltype(x) * p##x
+PULSE_FUNCS(MAKE_FUNC)
+#undef MAKE_FUNC
+
+#ifndef IN_IDE_PARSER
+#define pa_mainloop_new ppa_mainloop_new
+#define pa_mainloop_free ppa_mainloop_free
+#define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func
+#define pa_mainloop_run ppa_mainloop_run
+#define pa_mainloop_quit ppa_mainloop_quit
+#define pa_mainloop_get_api ppa_mainloop_get_api
+#define pa_context_new ppa_context_new
+#define pa_context_unref ppa_context_unref
+#define pa_context_get_state ppa_context_get_state
+#define pa_context_disconnect ppa_context_disconnect
+#define pa_context_set_state_callback ppa_context_set_state_callback
+#define pa_context_errno ppa_context_errno
+#define pa_context_connect ppa_context_connect
+#define pa_context_get_server_info ppa_context_get_server_info
+#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name
+#define pa_context_get_sink_info_list ppa_context_get_sink_info_list
+#define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name
+#define pa_context_get_source_info_list ppa_context_get_source_info_list
+#define pa_stream_new ppa_stream_new
+#define pa_stream_unref ppa_stream_unref
+#define pa_stream_disconnect ppa_stream_disconnect
+#define pa_stream_drop ppa_stream_drop
+#define pa_stream_set_write_callback ppa_stream_set_write_callback
+#define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr
+#define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr
+#define pa_stream_get_sample_spec ppa_stream_get_sample_spec
+#define pa_stream_get_time ppa_stream_get_time
+#define pa_stream_set_read_callback ppa_stream_set_read_callback
+#define pa_stream_set_state_callback ppa_stream_set_state_callback
+#define pa_stream_set_moved_callback ppa_stream_set_moved_callback
+#define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback
+#define pa_stream_connect_record ppa_stream_connect_record
+#define pa_stream_connect_playback ppa_stream_connect_playback
+#define pa_stream_readable_size ppa_stream_readable_size
+#define pa_stream_writable_size ppa_stream_writable_size
+#define pa_stream_is_corked ppa_stream_is_corked
+#define pa_stream_cork ppa_stream_cork
+#define pa_stream_is_suspended ppa_stream_is_suspended
+#define pa_stream_get_device_name ppa_stream_get_device_name
+#define pa_stream_get_latency ppa_stream_get_latency
+#define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback
+#define pa_stream_begin_write ppa_stream_begin_write
+#define pa_channel_map_init_auto ppa_channel_map_init_auto
+#define pa_channel_map_parse ppa_channel_map_parse
+#define pa_channel_map_snprint ppa_channel_map_snprint
+#define pa_channel_map_equal ppa_channel_map_equal
+#define pa_channel_map_superset ppa_channel_map_superset
+#define pa_channel_position_to_string ppa_channel_position_to_string
+#define pa_operation_get_state ppa_operation_get_state
+#define pa_operation_unref ppa_operation_unref
+#define pa_sample_spec_valid ppa_sample_spec_valid
+#define pa_frame_size ppa_frame_size
+#define pa_strerror ppa_strerror
+#define pa_stream_get_state ppa_stream_get_state
+#define pa_stream_peek ppa_stream_peek
+#define pa_stream_write ppa_stream_write
+#define pa_xfree ppa_xfree
+#define pa_path_get_filename ppa_path_get_filename
+#define pa_get_binary_name ppa_get_binary_name
+#define pa_xmalloc ppa_xmalloc
+#endif /* IN_IDE_PARSER */
+
+#endif
+
+
+constexpr pa_channel_map MonoChanMap{
+    1, {PA_CHANNEL_POSITION_MONO}
+}, StereoChanMap{
+    2, {PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT}
+}, QuadChanMap{
+    4, {
+        PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+        PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT
+    }
+}, X51ChanMap{
+    6, {
+        PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+        PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+        PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
+    }
+}, X51RearChanMap{
+    6, {
+        PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+        PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+        PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT
+    }
+}, X61ChanMap{
+    7, {
+        PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+        PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+        PA_CHANNEL_POSITION_REAR_CENTER,
+        PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
+    }
+}, X71ChanMap{
+    8, {
+        PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+        PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+        PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+        PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT
+    }
+};
+
+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)
+{ return pa_stream_flags_t(int(lhs) | int(rhs)); }
+inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs)
+{
+    lhs = lhs | rhs;
+    return lhs;
+}
+inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs)
+{
+    lhs = pa_stream_flags_t(int(lhs) & rhs);
+    return lhs;
+}
+
+inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs)
+{
+    lhs = pa_context_flags_t(int(lhs) | int(rhs));
+    return lhs;
+}
+
+
+struct DevMap {
+    std::string name;
+    std::string device_name;
+};
+
+bool checkName(const al::span<const DevMap> list, const std::string &name)
+{
+    auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
+    return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
+}
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+/* Global flags and properties */
+pa_context_flags_t pulse_ctx_flags;
+
+class PulseMainloop {
+    std::thread mThread;
+    std::mutex mMutex;
+    std::condition_variable mCondVar;
+    pa_mainloop *mMainloop{nullptr};
+
+    static int poll(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) noexcept
+    {
+        auto plock = static_cast<std::unique_lock<std::mutex>*>(userdata);
+        plock->unlock();
+        int r{::poll(ufds, nfds, timeout)};
+        plock->lock();
+        return r;
+    }
+
+    int mainloop_proc()
+    {
+        SetRTPriority();
+
+        std::unique_lock<std::mutex> plock{mMutex};
+        mMainloop = pa_mainloop_new();
+
+        pa_mainloop_set_poll_func(mMainloop, poll, &plock);
+        mCondVar.notify_all();
+
+        int ret{};
+        pa_mainloop_run(mMainloop, &ret);
+
+        pa_mainloop_free(mMainloop);
+        mMainloop = nullptr;
+
+        return ret;
+    }
+
+public:
+    ~PulseMainloop()
+    {
+        if(mThread.joinable())
+        {
+            {
+                std::lock_guard<std::mutex> _{mMutex};
+                pa_mainloop_quit(mMainloop, 0);
+            }
+            mThread.join();
+        }
+    }
+
+    std::unique_lock<std::mutex> getUniqueLock() { return std::unique_lock<std::mutex>{mMutex}; }
+    std::condition_variable &getCondVar() noexcept { return mCondVar; }
+
+    void contextStateCallback(pa_context *context) noexcept
+    {
+        pa_context_state_t state{pa_context_get_state(context)};
+        if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state))
+            mCondVar.notify_all();
+    }
+    static void contextStateCallbackC(pa_context *context, void *pdata) noexcept
+    { static_cast<PulseMainloop*>(pdata)->contextStateCallback(context); }
+
+    void streamStateCallback(pa_stream *stream) noexcept
+    {
+        pa_stream_state_t state{pa_stream_get_state(stream)};
+        if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state))
+            mCondVar.notify_all();
+    }
+    static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulseMainloop*>(pdata)->streamStateCallback(stream); }
+
+    void streamSuccessCallback(pa_stream*, int) noexcept
+    { mCondVar.notify_all(); }
+    static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept
+    { static_cast<PulseMainloop*>(pdata)->streamSuccessCallback(stream, success); }
+
+    void waitForOperation(pa_operation *op, std::unique_lock<std::mutex> &plock)
+    {
+        if(op)
+        {
+            mCondVar.wait(plock,
+                [op]() -> bool { return pa_operation_get_state(op) != PA_OPERATION_RUNNING; });
+            pa_operation_unref(op);
+        }
+    }
+
+    pa_context *connectContext(std::unique_lock<std::mutex> &plock);
+
+    pa_stream *connectStream(const char *device_name, std::unique_lock<std::mutex> &plock,
+        pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec,
+        pa_channel_map *chanmap, BackendType type);
+
+    void close(pa_context *context, pa_stream *stream);
+
+
+    void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
+    {
+        if(eol)
+        {
+            mCondVar.notify_all();
+            return;
+        }
+
+        /* Skip this device is if it's already in the list. */
+        auto match_devname = [info](const DevMap &entry) -> bool
+        { return entry.device_name == info->name; };
+        if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_devname) != PlaybackDevices.cend())
+            return;
+
+        /* Make sure the display name (description) is unique. Append a number
+         * counter as needed.
+         */
+        int count{1};
+        std::string newname{info->description};
+        while(checkName(PlaybackDevices, newname))
+        {
+            newname = info->description;
+            newname += " #";
+            newname += std::to_string(++count);
+        }
+        PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name});
+        DevMap &newentry = PlaybackDevices.back();
+
+        TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
+    }
+    static void deviceSinkCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
+    { static_cast<PulseMainloop*>(pdata)->deviceSinkCallback(context, info, eol); }
+
+    void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept
+    {
+        if(eol)
+        {
+            mCondVar.notify_all();
+            return;
+        }
+
+        /* Skip this device is if it's already in the list. */
+        auto match_devname = [info](const DevMap &entry) -> bool
+        { return entry.device_name == info->name; };
+        if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_devname) != CaptureDevices.cend())
+            return;
+
+        /* Make sure the display name (description) is unique. Append a number
+         * counter as needed.
+         */
+        int count{1};
+        std::string newname{info->description};
+        while(checkName(CaptureDevices, newname))
+        {
+            newname = info->description;
+            newname += " #";
+            newname += std::to_string(++count);
+        }
+        CaptureDevices.emplace_back(DevMap{std::move(newname), info->name});
+        DevMap &newentry = CaptureDevices.back();
+
+        TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str());
+    }
+    static void deviceSourceCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept
+    { static_cast<PulseMainloop*>(pdata)->deviceSourceCallback(context, info, eol); }
+
+    void probePlaybackDevices();
+    void probeCaptureDevices();
+};
+
+
+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)};
+    if(!context) throw al::backend_exception{al::backend_error::OutOfMemory,
+        "pa_context_new() failed"};
+
+    pa_context_set_state_callback(context, &contextStateCallbackC, this);
+
+    int err;
+    if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0)
+    {
+        pa_context_state_t state;
+        while((state=pa_context_get_state(context)) != PA_CONTEXT_READY)
+        {
+            if(!PA_CONTEXT_IS_GOOD(state))
+            {
+                err = pa_context_errno(context);
+                if(err > 0)  err = -err;
+                break;
+            }
+
+            mCondVar.wait(plock);
+        }
+    }
+    pa_context_set_state_callback(context, nullptr, nullptr);
+
+    if(err < 0)
+    {
+        pa_context_unref(context);
+        throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)",
+            pa_strerror(err)};
+    }
+
+    return context;
+}
+
+pa_stream *PulseMainloop::connectStream(const char *device_name,
+    std::unique_lock<std::mutex> &plock, pa_context *context, pa_stream_flags_t flags,
+    pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type)
+{
+    const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"};
+    pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)};
+    if(!stream)
+        throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)",
+            pa_strerror(pa_context_errno(context))};
+
+    pa_stream_set_state_callback(stream, &streamStateCallbackC, this);
+
+    int err{(type==BackendType::Playback) ?
+        pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) :
+        pa_stream_connect_record(stream, device_name, attr, flags)};
+    if(err < 0)
+    {
+        pa_stream_unref(stream);
+        throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect (%s)",
+            stream_id, pa_strerror(err)};
+    }
+
+    pa_stream_state_t state;
+    while((state=pa_stream_get_state(stream)) != PA_STREAM_READY)
+    {
+        if(!PA_STREAM_IS_GOOD(state))
+        {
+            err = pa_context_errno(context);
+            pa_stream_unref(stream);
+            throw al::backend_exception{al::backend_error::DeviceError,
+                "%s did not get ready (%s)", stream_id, pa_strerror(err)};
+        }
+
+        mCondVar.wait(plock);
+    }
+    pa_stream_set_state_callback(stream, nullptr, nullptr);
+
+    return stream;
+}
+
+void PulseMainloop::close(pa_context *context, pa_stream *stream)
+{
+    std::lock_guard<std::mutex> _{mMutex};
+    if(stream)
+    {
+        pa_stream_set_state_callback(stream, nullptr, nullptr);
+        pa_stream_set_moved_callback(stream, nullptr, nullptr);
+        pa_stream_set_write_callback(stream, nullptr, nullptr);
+        pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr);
+        pa_stream_disconnect(stream);
+        pa_stream_unref(stream);
+    }
+
+    pa_context_disconnect(context);
+    pa_context_unref(context);
+}
+
+
+void PulseMainloop::probePlaybackDevices()
+{
+    pa_context *context{};
+    pa_stream *stream{};
+
+    PlaybackDevices.clear();
+    try {
+        std::unique_lock<std::mutex> plock{mMutex};
+
+        context = connectContext(plock);
+        pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr,
+            &deviceSinkCallbackC, this)};
+        waitForOperation(op, plock);
+
+        op = pa_context_get_sink_info_list(context, &deviceSinkCallbackC, this);
+        waitForOperation(op, plock);
+
+        pa_context_disconnect(context);
+        pa_context_unref(context);
+        context = nullptr;
+    }
+    catch(std::exception &e) {
+        ERR("Error enumerating devices: %s\n", e.what());
+        if(context) close(context, stream);
+    }
+}
+
+void PulseMainloop::probeCaptureDevices()
+{
+    pa_context *context{};
+    pa_stream *stream{};
+
+    CaptureDevices.clear();
+    try {
+        std::unique_lock<std::mutex> plock{mMutex};
+
+        context = connectContext(plock);
+        pa_operation *op{pa_context_get_source_info_by_name(context, nullptr,
+            &deviceSourceCallbackC, this)};
+        waitForOperation(op, plock);
+
+        op = pa_context_get_source_info_list(context, &deviceSourceCallbackC, this);
+        waitForOperation(op, plock);
+
+        pa_context_disconnect(context);
+        pa_context_unref(context);
+        context = nullptr;
+    }
+    catch(std::exception &e) {
+        ERR("Error enumerating devices: %s\n", e.what());
+        if(context) close(context, stream);
+    }
+}
+
+
+/* Used for initial connection test and enumeration. */
+PulseMainloop gGlobalMainloop;
+
+
+struct PulsePlayback final : public BackendBase {
+    PulsePlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~PulsePlayback() override;
+
+    void bufferAttrCallback(pa_stream *stream) noexcept;
+    static void bufferAttrCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->bufferAttrCallback(stream); }
+
+    void streamStateCallback(pa_stream *stream) noexcept;
+    static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->streamStateCallback(stream); }
+
+    void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept;
+    static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->streamWriteCallback(stream, nbytes); }
+
+    void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept;
+    static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->sinkInfoCallback(context, info, eol); }
+
+    void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept;
+    static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->sinkNameCallback(context, info, eol); }
+
+    void streamMovedCallback(pa_stream *stream) noexcept;
+    static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulsePlayback*>(pdata)->streamMovedCallback(stream); }
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+    ClockLatency getClockLatency() override;
+
+    PulseMainloop mMainloop;
+
+    al::optional<std::string> mDeviceName{al::nullopt};
+
+    pa_buffer_attr mAttr;
+    pa_sample_spec mSpec;
+
+    pa_stream *mStream{nullptr};
+    pa_context *mContext{nullptr};
+
+    uint mFrameSize{0u};
+
+    DEF_NEWDEL(PulsePlayback)
+};
+
+PulsePlayback::~PulsePlayback()
+{
+    if(!mContext)
+        return;
+
+    mMainloop.close(mContext, mStream);
+    mContext = nullptr;
+    mStream = nullptr;
+}
+
+
+void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept
+{
+    /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new
+     * buffer attributes? Changing UpdateSize will change the ALC_REFRESH
+     * property, which probably shouldn't change between device resets. But
+     * leaving it alone means ALC_REFRESH will be off.
+     */
+    mAttr = *(pa_stream_get_buffer_attr(stream));
+    TRACE("minreq=%d, tlength=%d, prebuf=%d\n", mAttr.minreq, mAttr.tlength, mAttr.prebuf);
+}
+
+void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept
+{
+    if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
+    {
+        ERR("Received stream failure!\n");
+        mDevice->handleDisconnect("Playback stream failure");
+    }
+    mMainloop.getCondVar().notify_all();
+}
+
+void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept
+{
+    do {
+        pa_free_cb_t free_func{nullptr};
+        auto buflen = static_cast<size_t>(-1);
+        void *buf;
+        if UNLIKELY(pa_stream_begin_write(stream, &buf, &buflen) || !buf)
+        {
+            buflen = nbytes;
+            buf = pa_xmalloc(buflen);
+            free_func = pa_xfree;
+        }
+        else
+            buflen = minz(buflen, nbytes);
+        nbytes -= buflen;
+
+        mDevice->renderSamples(buf, static_cast<uint>(buflen/mFrameSize), mSpec.channels);
+
+        int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)};
+        if UNLIKELY(ret != PA_OK)
+            ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret));
+    } while(nbytes > 0);
+}
+
+void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
+{
+    struct ChannelMap {
+        DevFmtChannels fmt;
+        pa_channel_map map;
+    };
+    static constexpr std::array<ChannelMap,7> chanmaps{{
+        { DevFmtX71, X71ChanMap },
+        { DevFmtX61, X61ChanMap },
+        { DevFmtX51, X51ChanMap },
+        { DevFmtX51Rear, X51RearChanMap },
+        { DevFmtQuad, QuadChanMap },
+        { DevFmtStereo, StereoChanMap },
+        { DevFmtMono, MonoChanMap }
+    }};
+
+    if(eol)
+    {
+        mMainloop.getCondVar().notify_all();
+        return;
+    }
+
+    auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(),
+        [info](const ChannelMap &chanmap) -> bool
+        { return pa_channel_map_superset(&info->channel_map, &chanmap.map); }
+    );
+    if(chaniter != chanmaps.cend())
+    {
+        if(!mDevice->Flags.test(ChannelsRequest))
+            mDevice->FmtChans = chaniter->fmt;
+    }
+    else
+    {
+        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);
+    }
+
+    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);
+}
+
+void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept
+{
+    if(eol)
+    {
+        mMainloop.getCondVar().notify_all();
+        return;
+    }
+    mDevice->DeviceName = info->description;
+}
+
+void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept
+{
+    mDeviceName = pa_stream_get_device_name(stream);
+    TRACE("Stream moved to %s\n", mDeviceName->c_str());
+}
+
+
+void PulsePlayback::open(const char *name)
+{
+    const char *pulse_name{nullptr};
+    const char *dev_name{nullptr};
+
+    if(name)
+    {
+        if(PlaybackDevices.empty())
+            mMainloop.probePlaybackDevices();
+
+        auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+            [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};
+        pulse_name = iter->device_name.c_str();
+        dev_name = iter->name.c_str();
+    }
+
+    auto plock = mMainloop.getUniqueLock();
+    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};
+    if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1))
+        flags |= PA_STREAM_DONT_MOVE;
+
+    pa_sample_spec spec{};
+    spec.format = PA_SAMPLE_S16NE;
+    spec.rate = 44100;
+    spec.channels = 2;
+
+    if(!pulse_name)
+    {
+        static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT");
+        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_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
+    mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
+
+    mDeviceName = pulse_name ? al::make_optional<std::string>(pulse_name) : al::nullopt;
+    if(!dev_name)
+    {
+        pa_operation *op{pa_context_get_sink_info_by_name(mContext,
+            pa_stream_get_device_name(mStream), &PulsePlayback::sinkNameCallbackC, this)};
+        mMainloop.waitForOperation(op, plock);
+    }
+    else
+        mDevice->DeviceName = dev_name;
+}
+
+bool PulsePlayback::reset()
+{
+    auto plock = mMainloop.getUniqueLock();
+    const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr;
+
+    if(mStream)
+    {
+        pa_stream_set_state_callback(mStream, nullptr, nullptr);
+        pa_stream_set_moved_callback(mStream, nullptr, nullptr);
+        pa_stream_set_write_callback(mStream, nullptr, nullptr);
+        pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr);
+        pa_stream_disconnect(mStream);
+        pa_stream_unref(mStream);
+        mStream = nullptr;
+    }
+
+    pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName,
+        &PulsePlayback::sinkInfoCallbackC, this)};
+    mMainloop.waitForOperation(op, plock);
+
+    pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING |
+        PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS};
+    if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1))
+        flags |= PA_STREAM_DONT_MOVE;
+    if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0))
+    {
+        /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some
+         * reason. So if the user wants to adjust the overall device latency,
+         * we can't ask to get write signals as soon as minreq is reached.
+         */
+        flags &= ~PA_STREAM_EARLY_REQUESTS;
+        flags |= PA_STREAM_ADJUST_LATENCY;
+    }
+    if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0)
+        || !mDevice->Flags.test(FrequencyRequest))
+        flags |= PA_STREAM_FIX_RATE;
+
+    pa_channel_map chanmap{};
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        chanmap = MonoChanMap;
+        break;
+    case DevFmtAmbi3D:
+        mDevice->FmtChans = DevFmtStereo;
+        /*fall-through*/
+    case DevFmtStereo:
+        chanmap = StereoChanMap;
+        break;
+    case DevFmtQuad:
+        chanmap = QuadChanMap;
+        break;
+    case DevFmtX51:
+        chanmap = X51ChanMap;
+        break;
+    case DevFmtX51Rear:
+        chanmap = X51RearChanMap;
+        break;
+    case DevFmtX61:
+        chanmap = X61ChanMap;
+        break;
+    case DevFmtX71:
+        chanmap = X71ChanMap;
+        break;
+    }
+    SetChannelOrderFromMap(mDevice, chanmap);
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        mDevice->FmtType = DevFmtUByte;
+        /* fall-through */
+    case DevFmtUByte:
+        mSpec.format = PA_SAMPLE_U8;
+        break;
+    case DevFmtUShort:
+        mDevice->FmtType = DevFmtShort;
+        /* fall-through */
+    case DevFmtShort:
+        mSpec.format = PA_SAMPLE_S16NE;
+        break;
+    case DevFmtUInt:
+        mDevice->FmtType = DevFmtInt;
+        /* fall-through */
+    case DevFmtInt:
+        mSpec.format = PA_SAMPLE_S32NE;
+        break;
+    case DevFmtFloat:
+        mSpec.format = PA_SAMPLE_FLOAT32NE;
+        break;
+    }
+    mSpec.rate = mDevice->Frequency;
+    mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
+    if(pa_sample_spec_valid(&mSpec) == 0)
+        throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"};
+
+    const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
+    mAttr.maxlength = ~0u;
+    mAttr.tlength = mDevice->BufferSize * frame_size;
+    mAttr.prebuf = 0u;
+    mAttr.minreq = mDevice->UpdateSize * frame_size;
+    mAttr.fragsize = ~0u;
+
+    mStream = mMainloop.connectStream(deviceName, plock, mContext, flags, &mAttr, &mSpec,
+        &chanmap, BackendType::Playback);
+
+    pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this);
+    pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this);
+
+    mSpec = *(pa_stream_get_sample_spec(mStream));
+    mFrameSize = static_cast<uint>(pa_frame_size(&mSpec));
+
+    if(mDevice->Frequency != mSpec.rate)
+    {
+        /* Server updated our playback rate, so modify the buffer attribs
+         * accordingly.
+         */
+        const auto scale = static_cast<double>(mSpec.rate) / mDevice->Frequency;
+        const auto perlen = static_cast<uint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0,
+            8192.0));
+        const auto buflen = static_cast<uint>(clampd(scale*mDevice->BufferSize + 0.5, perlen*2,
+            std::numeric_limits<int>::max()/mFrameSize));
+
+        mAttr.maxlength = ~0u;
+        mAttr.tlength = buflen * mFrameSize;
+        mAttr.prebuf = 0u;
+        mAttr.minreq = perlen * mFrameSize;
+
+        op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC,
+            &mMainloop);
+        mMainloop.waitForOperation(op, plock);
+
+        mDevice->Frequency = mSpec.rate;
+    }
+
+    pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this);
+    bufferAttrCallback(mStream);
+
+    mDevice->BufferSize = mAttr.tlength / mFrameSize;
+    mDevice->UpdateSize = mAttr.minreq / mFrameSize;
+
+    return true;
+}
+
+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})
+    {
+        prebuf = minz(prebuf, pa_stream_writable_size(mStream));
+
+        void *buf{pa_xmalloc(prebuf)};
+        switch(mSpec.format)
+        {
+        case PA_SAMPLE_U8:
+            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x80);
+            break;
+        case PA_SAMPLE_ALAW:
+            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0xD5);
+            break;
+        case PA_SAMPLE_ULAW:
+            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x7f);
+            break;
+        default:
+            std::fill_n(static_cast<uint8_t*>(buf), prebuf, 0x00);
+            break;
+        }
+        pa_stream_write(mStream, buf, prebuf, pa_xfree, 0, PA_SEEK_RELATIVE);
+    }
+
+    mMainloop.waitForOperation(op, plock);
+}
+
+void PulsePlayback::stop()
+{
+    auto plock = mMainloop.getUniqueLock();
+
+    pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC,
+        &mMainloop)};
+    mMainloop.waitForOperation(op, plock);
+    pa_stream_set_write_callback(mStream, nullptr, nullptr);
+}
+
+
+ClockLatency PulsePlayback::getClockLatency()
+{
+    ClockLatency ret;
+    pa_usec_t latency;
+    int neg, err;
+
+    {
+        auto plock = mMainloop.getUniqueLock();
+        ret.ClockTime = GetDeviceClockTime(mDevice);
+        err = pa_stream_get_latency(mStream, &latency, &neg);
+    }
+
+    if UNLIKELY(err != 0)
+    {
+        /* If err = -PA_ERR_NODATA, it means we were called too soon after
+         * starting the stream and no timing info has been received from the
+         * server yet. Give a generic value since nothing better is available.
+         */
+        if(err != -PA_ERR_NODATA)
+            ERR("Failed to get stream latency: 0x%x\n", err);
+        latency = mDevice->BufferSize - mDevice->UpdateSize;
+        neg = 0;
+    }
+    else if UNLIKELY(neg)
+        latency = 0;
+    ret.Latency = std::chrono::microseconds{latency};
+
+    return ret;
+}
+
+
+struct PulseCapture final : public BackendBase {
+    PulseCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~PulseCapture() override;
+
+    void streamStateCallback(pa_stream *stream) noexcept;
+    static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulseCapture*>(pdata)->streamStateCallback(stream); }
+
+    void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept;
+    static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept
+    { static_cast<PulseCapture*>(pdata)->sourceNameCallback(context, info, eol); }
+
+    void streamMovedCallback(pa_stream *stream) noexcept;
+    static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept
+    { static_cast<PulseCapture*>(pdata)->streamMovedCallback(stream); }
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+    ClockLatency getClockLatency() override;
+
+    PulseMainloop mMainloop;
+
+    al::optional<std::string> mDeviceName{al::nullopt};
+
+    uint mLastReadable{0u};
+    al::byte mSilentVal{};
+
+    al::span<const al::byte> mCapBuffer;
+    ssize_t mCapLen{0};
+
+    pa_buffer_attr mAttr{};
+    pa_sample_spec mSpec{};
+
+    pa_stream *mStream{nullptr};
+    pa_context *mContext{nullptr};
+
+    DEF_NEWDEL(PulseCapture)
+};
+
+PulseCapture::~PulseCapture()
+{
+    if(!mContext)
+        return;
+
+    mMainloop.close(mContext, mStream);
+    mContext = nullptr;
+    mStream = nullptr;
+}
+
+
+void PulseCapture::streamStateCallback(pa_stream *stream) noexcept
+{
+    if(pa_stream_get_state(stream) == PA_STREAM_FAILED)
+    {
+        ERR("Received stream failure!\n");
+        mDevice->handleDisconnect("Capture stream failure");
+    }
+    mMainloop.getCondVar().notify_all();
+}
+
+void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept
+{
+    if(eol)
+    {
+        mMainloop.getCondVar().notify_all();
+        return;
+    }
+    mDevice->DeviceName = info->description;
+}
+
+void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept
+{
+    mDeviceName = pa_stream_get_device_name(stream);
+    TRACE("Stream moved to %s\n", mDeviceName->c_str());
+}
+
+
+void PulseCapture::open(const char *name)
+{
+    const char *pulse_name{nullptr};
+    if(name)
+    {
+        if(CaptureDevices.empty())
+            mMainloop.probeCaptureDevices();
+
+        auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+            [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};
+        pulse_name = iter->device_name.c_str();
+        mDevice->DeviceName = iter->name;
+    }
+
+    auto plock = mMainloop.getUniqueLock();
+    mContext = mMainloop.connectContext(plock);
+
+    pa_channel_map chanmap{};
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        chanmap = MonoChanMap;
+        break;
+    case DevFmtStereo:
+        chanmap = StereoChanMap;
+        break;
+    case DevFmtQuad:
+        chanmap = QuadChanMap;
+        break;
+    case DevFmtX51:
+        chanmap = X51ChanMap;
+        break;
+    case DevFmtX51Rear:
+        chanmap = X51RearChanMap;
+        break;
+    case DevFmtX61:
+        chanmap = X61ChanMap;
+        break;
+    case DevFmtX71:
+        chanmap = X71ChanMap;
+        break;
+    case DevFmtAmbi3D:
+        throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
+            DevFmtChannelsString(mDevice->FmtChans)};
+    }
+    SetChannelOrderFromMap(mDevice, chanmap);
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtUByte:
+        mSilentVal = al::byte(0x80);
+        mSpec.format = PA_SAMPLE_U8;
+        break;
+    case DevFmtShort:
+        mSpec.format = PA_SAMPLE_S16NE;
+        break;
+    case DevFmtInt:
+        mSpec.format = PA_SAMPLE_S32NE;
+        break;
+    case DevFmtFloat:
+        mSpec.format = PA_SAMPLE_FLOAT32NE;
+        break;
+    case DevFmtByte:
+    case DevFmtUShort:
+    case DevFmtUInt:
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
+    }
+    mSpec.rate = mDevice->Frequency;
+    mSpec.channels = static_cast<uint8_t>(mDevice->channelsFromFmt());
+    if(pa_sample_spec_valid(&mSpec) == 0)
+        throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"};
+
+    const auto frame_size = static_cast<uint>(pa_frame_size(&mSpec));
+    const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)};
+    mAttr.minreq = ~0u;
+    mAttr.prebuf = ~0u;
+    mAttr.maxlength = samples * frame_size;
+    mAttr.tlength = ~0u;
+    mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size;
+
+    pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY};
+    if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", 1))
+        flags |= PA_STREAM_DONT_MOVE;
+
+    TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)");
+    mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap,
+        BackendType::Capture);
+
+    pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this);
+    pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this);
+
+    mDeviceName = pulse_name ? al::make_optional<std::string>(pulse_name) : al::nullopt;
+    if(mDevice->DeviceName.empty())
+    {
+        pa_operation *op{pa_context_get_source_info_by_name(mContext,
+            pa_stream_get_device_name(mStream), &PulseCapture::sourceNameCallbackC, this)};
+        mMainloop.waitForOperation(op, plock);
+    }
+}
+
+void PulseCapture::start()
+{
+    auto plock = mMainloop.getUniqueLock();
+    pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
+        &mMainloop)};
+    mMainloop.waitForOperation(op, plock);
+}
+
+void PulseCapture::stop()
+{
+    auto plock = mMainloop.getUniqueLock();
+    pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC,
+        &mMainloop)};
+    mMainloop.waitForOperation(op, plock);
+}
+
+void PulseCapture::captureSamples(al::byte *buffer, uint samples)
+{
+    al::span<al::byte> dstbuf{buffer, samples * pa_frame_size(&mSpec)};
+
+    /* Capture is done in fragment-sized chunks, so we loop until we get all
+     * that's available */
+    mLastReadable -= static_cast<uint>(dstbuf.size());
+    while(!dstbuf.empty())
+    {
+        if(!mCapBuffer.empty())
+        {
+            const size_t rem{minz(dstbuf.size(), mCapBuffer.size())};
+            if UNLIKELY(mCapLen < 0)
+                std::fill_n(dstbuf.begin(), rem, mSilentVal);
+            else
+                std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin());
+            dstbuf = dstbuf.subspan(rem);
+            mCapBuffer = mCapBuffer.subspan(rem);
+
+            continue;
+        }
+
+        if UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire))
+            break;
+
+        auto plock = mMainloop.getUniqueLock();
+        if(mCapLen != 0)
+        {
+            pa_stream_drop(mStream);
+            mCapBuffer = {};
+            mCapLen = 0;
+        }
+        const pa_stream_state_t state{pa_stream_get_state(mStream)};
+        if UNLIKELY(!PA_STREAM_IS_GOOD(state))
+        {
+            mDevice->handleDisconnect("Bad capture state: %u", state);
+            break;
+        }
+        const void *capbuf;
+        size_t caplen;
+        if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0)
+        {
+            mDevice->handleDisconnect("Failed retrieving capture samples: %s",
+                pa_strerror(pa_context_errno(mContext)));
+            break;
+        }
+        plock.unlock();
+
+        if(caplen == 0) break;
+        if UNLIKELY(!capbuf)
+            mCapLen = -static_cast<ssize_t>(caplen);
+        else
+            mCapLen = static_cast<ssize_t>(caplen);
+        mCapBuffer = {static_cast<const al::byte*>(capbuf), caplen};
+    }
+    if(!dstbuf.empty())
+        std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal);
+}
+
+uint PulseCapture::availableSamples()
+{
+    size_t readable{mCapBuffer.size()};
+
+    if(mDevice->Connected.load(std::memory_order_acquire))
+    {
+        auto plock = mMainloop.getUniqueLock();
+        size_t got{pa_stream_readable_size(mStream)};
+        if UNLIKELY(static_cast<ssize_t>(got) < 0)
+        {
+            const char *err{pa_strerror(static_cast<int>(got))};
+            ERR("pa_stream_readable_size() failed: %s\n", err);
+            mDevice->handleDisconnect("Failed getting readable size: %s", err);
+        }
+        else
+        {
+            const auto caplen = static_cast<size_t>(std::abs(mCapLen));
+            if(got > caplen) readable += got - caplen;
+        }
+    }
+
+    readable = std::min<size_t>(readable, std::numeric_limits<uint>::max());
+    mLastReadable = std::max(mLastReadable, static_cast<uint>(readable));
+    return mLastReadable / static_cast<uint>(pa_frame_size(&mSpec));
+}
+
+
+ClockLatency PulseCapture::getClockLatency()
+{
+    ClockLatency ret;
+    pa_usec_t latency;
+    int neg, err;
+
+    {
+        auto plock = mMainloop.getUniqueLock();
+        ret.ClockTime = GetDeviceClockTime(mDevice);
+        err = pa_stream_get_latency(mStream, &latency, &neg);
+    }
+
+    if UNLIKELY(err != 0)
+    {
+        ERR("Failed to get stream latency: 0x%x\n", err);
+        latency = 0;
+        neg = 0;
+    }
+    else if UNLIKELY(neg)
+        latency = 0;
+    ret.Latency = std::chrono::microseconds{latency};
+
+    return ret;
+}
+
+} // namespace
+
+
+bool PulseBackendFactory::init()
+{
+#ifdef HAVE_DYNLOAD
+    if(!pulse_handle)
+    {
+        bool ret{true};
+        std::string missing_funcs;
+
+#ifdef _WIN32
+#define PALIB "libpulse-0.dll"
+#elif defined(__APPLE__) && defined(__MACH__)
+#define PALIB "libpulse.0.dylib"
+#else
+#define PALIB "libpulse.so.0"
+#endif
+        pulse_handle = LoadLib(PALIB);
+        if(!pulse_handle)
+        {
+            WARN("Failed to load %s\n", PALIB);
+            return false;
+        }
+
+#define LOAD_FUNC(x) do {                                                     \
+    p##x = reinterpret_cast<decltype(p##x)>(GetSymbol(pulse_handle, #x));     \
+    if(!(p##x)) {                                                             \
+        ret = false;                                                          \
+        missing_funcs += "\n" #x;                                             \
+    }                                                                         \
+} while(0)
+        PULSE_FUNCS(LOAD_FUNC)
+#undef LOAD_FUNC
+
+        if(!ret)
+        {
+            WARN("Missing expected functions:%s\n", missing_funcs.c_str());
+            CloseLib(pulse_handle);
+            pulse_handle = nullptr;
+            return false;
+        }
+    }
+#endif /* HAVE_DYNLOAD */
+
+    pulse_ctx_flags = PA_CONTEXT_NOFLAGS;
+    if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1))
+        pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN;
+
+    try {
+        auto plock = gGlobalMainloop.getUniqueLock();
+        pa_context *context{gGlobalMainloop.connectContext(plock)};
+        pa_context_disconnect(context);
+        pa_context_unref(context);
+        return true;
+    }
+    catch(...) {
+        return false;
+    }
+}
+
+bool PulseBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string PulseBackendFactory::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:
+        gGlobalMainloop.probePlaybackDevices();
+        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        break;
+
+    case BackendType::Capture:
+        gGlobalMainloop.probeCaptureDevices();
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+
+    return outnames;
+}
+
+BackendPtr PulseBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new PulsePlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new PulseCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &PulseBackendFactory::getFactory()
+{
+    static PulseBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/pulseaudio.h

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

+ 0 - 1073
Engine/lib/openal-soft/Alc/backends/qsa.c

@@ -1,1073 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2011-2013 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 <stdlib.h>
-#include <stdio.h>
-#include <sched.h>
-#include <errno.h>
-#include <memory.h>
-#include <sys/select.h>
-#include <sys/asoundlib.h>
-#include <sys/neutrino.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "threads.h"
-
-#include "backends/base.h"
-
-
-typedef struct {
-    snd_pcm_t* pcmHandle;
-    int audio_fd;
-
-    snd_pcm_channel_setup_t  csetup;
-    snd_pcm_channel_params_t cparams;
-
-    ALvoid* buffer;
-    ALsizei size;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} qsa_data;
-
-typedef struct {
-    ALCchar* name;
-    int card;
-    int dev;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
-
-static vector_DevMap DeviceNameMap;
-static vector_DevMap CaptureNameMap;
-
-static const ALCchar qsaDevice[] = "QSA Default";
-
-static const struct {
-    int32_t format;
-} formatlist[] = {
-    {SND_PCM_SFMT_FLOAT_LE},
-    {SND_PCM_SFMT_S32_LE},
-    {SND_PCM_SFMT_U32_LE},
-    {SND_PCM_SFMT_S16_LE},
-    {SND_PCM_SFMT_U16_LE},
-    {SND_PCM_SFMT_S8},
-    {SND_PCM_SFMT_U8},
-    {0},
-};
-
-static const struct {
-    int32_t rate;
-} ratelist[] = {
-    {192000},
-    {176400},
-    {96000},
-    {88200},
-    {48000},
-    {44100},
-    {32000},
-    {24000},
-    {22050},
-    {16000},
-    {12000},
-    {11025},
-    {8000},
-    {0},
-};
-
-static const struct {
-    int32_t channels;
-} channellist[] = {
-    {8},
-    {7},
-    {6},
-    {4},
-    {2},
-    {1},
-    {0},
-};
-
-static void deviceList(int type, vector_DevMap *devmap)
-{
-    snd_ctl_t* handle;
-    snd_pcm_info_t pcminfo;
-    int max_cards, card, err, dev;
-    DevMap entry;
-    char name[1024];
-    struct snd_ctl_hw_info info;
-
-    max_cards = snd_cards();
-    if(max_cards < 0)
-        return;
-
-    VECTOR_RESIZE(*devmap, 0, max_cards+1);
-
-    entry.name = strdup(qsaDevice);
-    entry.card = 0;
-    entry.dev = 0;
-    VECTOR_PUSH_BACK(*devmap, entry);
-
-    for(card = 0;card < max_cards;card++)
-    {
-        if((err=snd_ctl_open(&handle, card)) < 0)
-            continue;
-
-        if((err=snd_ctl_hw_info(handle, &info)) < 0)
-        {
-            snd_ctl_close(handle);
-            continue;
-        }
-
-        for(dev = 0;dev < (int)info.pcmdevs;dev++)
-        {
-            if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0)
-                continue;
-
-            if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) ||
-               (type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE)))
-            {
-                snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev);
-                entry.name = strdup(name);
-                entry.card = card;
-                entry.dev = dev;
-
-                VECTOR_PUSH_BACK(*devmap, entry);
-                TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
-            }
-        }
-        snd_ctl_close(handle);
-    }
-}
-
-
-/* Wrappers to use an old-style backend with the new interface. */
-typedef struct PlaybackWrapper {
-    DERIVE_FROM_TYPE(ALCbackend);
-    qsa_data *ExtraData;
-} PlaybackWrapper;
-
-static void PlaybackWrapper_Construct(PlaybackWrapper *self, ALCdevice *device);
-static void PlaybackWrapper_Destruct(PlaybackWrapper *self);
-static ALCenum PlaybackWrapper_open(PlaybackWrapper *self, const ALCchar *name);
-static ALCboolean PlaybackWrapper_reset(PlaybackWrapper *self);
-static ALCboolean PlaybackWrapper_start(PlaybackWrapper *self);
-static void PlaybackWrapper_stop(PlaybackWrapper *self);
-static DECLARE_FORWARD2(PlaybackWrapper, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(PlaybackWrapper, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(PlaybackWrapper, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(PlaybackWrapper, ALCbackend, void, lock)
-static DECLARE_FORWARD(PlaybackWrapper, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(PlaybackWrapper)
-DEFINE_ALCBACKEND_VTABLE(PlaybackWrapper);
-
-
-FORCE_ALIGN static int qsa_proc_playback(void *ptr)
-{
-    PlaybackWrapper *self = ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data = self->ExtraData;
-    snd_pcm_channel_status_t status;
-    struct sched_param param;
-    struct timeval timeout;
-    char* write_ptr;
-    fd_set wfds;
-    ALint len;
-    int sret;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    /* Increase default 10 priority to 11 to avoid jerky sound */
-    SchedGet(0, 0, &param);
-    param.sched_priority=param.sched_curpriority+1;
-    SchedSet(0, 0, SCHED_NOCHANGE, &param);
-
-    const ALint frame_size = FrameSizeFromDevFmt(
-        device->FmtChans, device->FmtType, device->AmbiOrder
-    );
-
-    V0(device->Backend,lock)();
-    while(!ATOMIC_LOAD(&data->killNow, almemory_order_acquire))
-    {
-        FD_ZERO(&wfds);
-        FD_SET(data->audio_fd, &wfds);
-        timeout.tv_sec=2;
-        timeout.tv_usec=0;
-
-        /* Select also works like time slice to OS */
-        V0(device->Backend,unlock)();
-        sret = select(data->audio_fd+1, NULL, &wfds, NULL, &timeout);
-        V0(device->Backend,lock)();
-        if(sret == -1)
-        {
-            ERR("select error: %s\n", strerror(errno));
-            aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
-            break;
-        }
-        if(sret == 0)
-        {
-            ERR("select timeout\n");
-            continue;
-        }
-
-        len = data->size;
-        write_ptr = data->buffer;
-        aluMixData(device, write_ptr, len/frame_size);
-        while(len>0 && !ATOMIC_LOAD(&data->killNow, almemory_order_acquire))
-        {
-            int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
-            if(wrote <= 0)
-            {
-                if(errno==EAGAIN || errno==EWOULDBLOCK)
-                    continue;
-
-                memset(&status, 0, sizeof(status));
-                status.channel = SND_PCM_CHANNEL_PLAYBACK;
-
-                snd_pcm_plugin_status(data->pcmHandle, &status);
-
-                /* we need to reinitialize the sound channel if we've underrun the buffer */
-                if(status.status == SND_PCM_STATUS_UNDERRUN ||
-                   status.status == SND_PCM_STATUS_READY)
-                {
-                    if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0)
-                    {
-                        aluHandleDisconnect(device, "Playback recovery failed");
-                        break;
-                    }
-                }
-            }
-            else
-            {
-                write_ptr += wrote;
-                len -= wrote;
-            }
-        }
-    }
-    V0(device->Backend,unlock)();
-
-    return 0;
-}
-
-/************/
-/* Playback */
-/************/
-
-static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data;
-    int card, dev;
-    int status;
-
-    data = (qsa_data*)calloc(1, sizeof(qsa_data));
-    if(data == NULL)
-        return ALC_OUT_OF_MEMORY;
-    ATOMIC_INIT(&data->killNow, AL_TRUE);
-
-    if(!deviceName)
-        deviceName = qsaDevice;
-
-    if(strcmp(deviceName, qsaDevice) == 0)
-        status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
-    else
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(DeviceNameMap) == 0)
-            deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
-
-#define MATCH_DEVNAME(iter) ((iter)->name && strcmp(deviceName, (iter)->name)==0)
-        VECTOR_FIND_IF(iter, const DevMap, DeviceNameMap, MATCH_DEVNAME);
-#undef MATCH_DEVNAME
-        if(iter == VECTOR_END(DeviceNameMap))
-        {
-            free(data);
-            return ALC_INVALID_DEVICE;
-        }
-
-        status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK);
-    }
-
-    if(status < 0)
-    {
-        free(data);
-        return ALC_INVALID_DEVICE;
-    }
-
-    data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK);
-    if(data->audio_fd < 0)
-    {
-        snd_pcm_close(data->pcmHandle);
-        free(data);
-        return ALC_INVALID_DEVICE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, deviceName);
-    self->ExtraData = data;
-
-    return ALC_NO_ERROR;
-}
-
-static void qsa_close_playback(PlaybackWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-
-    if (data->buffer!=NULL)
-    {
-        free(data->buffer);
-        data->buffer=NULL;
-    }
-
-    snd_pcm_close(data->pcmHandle);
-    free(data);
-
-    self->ExtraData = NULL;
-}
-
-static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data = self->ExtraData;
-    int32_t format=-1;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-             format=SND_PCM_SFMT_S8;
-             break;
-        case DevFmtUByte:
-             format=SND_PCM_SFMT_U8;
-             break;
-        case DevFmtShort:
-             format=SND_PCM_SFMT_S16_LE;
-             break;
-        case DevFmtUShort:
-             format=SND_PCM_SFMT_U16_LE;
-             break;
-        case DevFmtInt:
-             format=SND_PCM_SFMT_S32_LE;
-             break;
-        case DevFmtUInt:
-             format=SND_PCM_SFMT_U32_LE;
-             break;
-        case DevFmtFloat:
-             format=SND_PCM_SFMT_FLOAT_LE;
-             break;
-    }
-
-    /* we actually don't want to block on writes */
-    snd_pcm_nonblock_mode(data->pcmHandle, 1);
-    /* Disable mmap to control data transfer to the audio device */
-    snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
-    snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
-
-    // configure a sound channel
-    memset(&data->cparams, 0, sizeof(data->cparams));
-    data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK;
-    data->cparams.mode=SND_PCM_MODE_BLOCK;
-    data->cparams.start_mode=SND_PCM_START_FULL;
-    data->cparams.stop_mode=SND_PCM_STOP_STOP;
-
-    data->cparams.buf.block.frag_size=device->UpdateSize *
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    data->cparams.buf.block.frags_max=device->NumUpdates;
-    data->cparams.buf.block.frags_min=device->NumUpdates;
-
-    data->cparams.format.interleave=1;
-    data->cparams.format.rate=device->Frequency;
-    data->cparams.format.voices=ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    data->cparams.format.format=format;
-
-    if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
-    {
-        int original_rate=data->cparams.format.rate;
-        int original_voices=data->cparams.format.voices;
-        int original_format=data->cparams.format.format;
-        int it;
-        int jt;
-
-        for (it=0; it<1; it++)
-        {
-            /* Check for second pass */
-            if (it==1)
-            {
-                original_rate=ratelist[0].rate;
-                original_voices=channellist[0].channels;
-                original_format=formatlist[0].format;
-            }
-
-            do {
-                /* At first downgrade sample format */
-                jt=0;
-                do {
-                    if (formatlist[jt].format==data->cparams.format.format)
-                    {
-                        data->cparams.format.format=formatlist[jt+1].format;
-                        break;
-                    }
-                    if (formatlist[jt].format==0)
-                    {
-                        data->cparams.format.format=0;
-                        break;
-                    }
-                    jt++;
-                } while(1);
-
-                if (data->cparams.format.format==0)
-                {
-                    data->cparams.format.format=original_format;
-
-                    /* At secod downgrade sample rate */
-                    jt=0;
-                    do {
-                        if (ratelist[jt].rate==data->cparams.format.rate)
-                        {
-                            data->cparams.format.rate=ratelist[jt+1].rate;
-                            break;
-                        }
-                        if (ratelist[jt].rate==0)
-                        {
-                            data->cparams.format.rate=0;
-                            break;
-                        }
-                        jt++;
-                    } while(1);
-
-                    if (data->cparams.format.rate==0)
-                    {
-                        data->cparams.format.rate=original_rate;
-                        data->cparams.format.format=original_format;
-
-                        /* At third downgrade channels number */
-                        jt=0;
-                        do {
-                            if(channellist[jt].channels==data->cparams.format.voices)
-                            {
-                                data->cparams.format.voices=channellist[jt+1].channels;
-                                break;
-                            }
-                            if (channellist[jt].channels==0)
-                            {
-                                data->cparams.format.voices=0;
-                                break;
-                            }
-                           jt++;
-                        } while(1);
-                    }
-
-                    if (data->cparams.format.voices==0)
-                    {
-                        break;
-                    }
-                }
-
-                data->cparams.buf.block.frag_size=device->UpdateSize*
-                    data->cparams.format.voices*
-                    snd_pcm_format_width(data->cparams.format.format)/8;
-                data->cparams.buf.block.frags_max=device->NumUpdates;
-                data->cparams.buf.block.frags_min=device->NumUpdates;
-                if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
-                {
-                    continue;
-                }
-                else
-                {
-                    break;
-                }
-            } while(1);
-
-            if (data->cparams.format.voices!=0)
-            {
-                break;
-            }
-        }
-
-        if (data->cparams.format.voices==0)
-        {
-            return ALC_FALSE;
-        }
-    }
-
-    if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0)
-    {
-        return ALC_FALSE;
-    }
-
-    memset(&data->csetup, 0, sizeof(data->csetup));
-    data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK;
-    if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0)
-    {
-        return ALC_FALSE;
-    }
-
-    /* now fill back to the our AL device */
-    device->Frequency=data->cparams.format.rate;
-
-    switch (data->cparams.format.voices)
-    {
-        case 1:
-             device->FmtChans=DevFmtMono;
-             break;
-        case 2:
-             device->FmtChans=DevFmtStereo;
-             break;
-        case 4:
-             device->FmtChans=DevFmtQuad;
-             break;
-        case 6:
-             device->FmtChans=DevFmtX51;
-             break;
-        case 7:
-             device->FmtChans=DevFmtX61;
-             break;
-        case 8:
-             device->FmtChans=DevFmtX71;
-             break;
-        default:
-             device->FmtChans=DevFmtMono;
-             break;
-    }
-
-    switch (data->cparams.format.format)
-    {
-        case SND_PCM_SFMT_S8:
-             device->FmtType=DevFmtByte;
-             break;
-        case SND_PCM_SFMT_U8:
-             device->FmtType=DevFmtUByte;
-             break;
-        case SND_PCM_SFMT_S16_LE:
-             device->FmtType=DevFmtShort;
-             break;
-        case SND_PCM_SFMT_U16_LE:
-             device->FmtType=DevFmtUShort;
-             break;
-        case SND_PCM_SFMT_S32_LE:
-             device->FmtType=DevFmtInt;
-             break;
-        case SND_PCM_SFMT_U32_LE:
-             device->FmtType=DevFmtUInt;
-             break;
-        case SND_PCM_SFMT_FLOAT_LE:
-             device->FmtType=DevFmtFloat;
-             break;
-        default:
-             device->FmtType=DevFmtShort;
-             break;
-    }
-
-    SetDefaultChannelOrder(device);
-
-    device->UpdateSize=data->csetup.buf.block.frag_size/
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    device->NumUpdates=data->csetup.buf.block.frags;
-
-    data->size=data->csetup.buf.block.frag_size;
-    data->buffer=malloc(data->size);
-    if (!data->buffer)
-    {
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static ALCboolean qsa_start_playback(PlaybackWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-
-    ATOMIC_STORE(&data->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&data->thread, qsa_proc_playback, self) != althrd_success)
-        return ALC_FALSE;
-
-    return ALC_TRUE;
-}
-
-static void qsa_stop_playback(PlaybackWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-    int res;
-
-    if(ATOMIC_EXCHANGE(&data->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(data->thread, &res);
-}
-
-
-static void PlaybackWrapper_Construct(PlaybackWrapper *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(PlaybackWrapper, ALCbackend, self);
-
-    self->ExtraData = NULL;
-}
-
-static void PlaybackWrapper_Destruct(PlaybackWrapper *self)
-{
-    if(self->ExtraData)
-        qsa_close_playback(self);
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static ALCenum PlaybackWrapper_open(PlaybackWrapper *self, const ALCchar *name)
-{
-    return qsa_open_playback(self, name);
-}
-
-static ALCboolean PlaybackWrapper_reset(PlaybackWrapper *self)
-{
-    return qsa_reset_playback(self);
-}
-
-static ALCboolean PlaybackWrapper_start(PlaybackWrapper *self)
-{
-    return qsa_start_playback(self);
-}
-
-static void PlaybackWrapper_stop(PlaybackWrapper *self)
-{
-    qsa_stop_playback(self);
-}
-
-
-
-/***********/
-/* Capture */
-/***********/
-
-typedef struct CaptureWrapper {
-    DERIVE_FROM_TYPE(ALCbackend);
-    qsa_data *ExtraData;
-} CaptureWrapper;
-
-static void CaptureWrapper_Construct(CaptureWrapper *self, ALCdevice *device);
-static void CaptureWrapper_Destruct(CaptureWrapper *self);
-static ALCenum CaptureWrapper_open(CaptureWrapper *self, const ALCchar *name);
-static DECLARE_FORWARD(CaptureWrapper, ALCbackend, ALCboolean, reset)
-static ALCboolean CaptureWrapper_start(CaptureWrapper *self);
-static void CaptureWrapper_stop(CaptureWrapper *self);
-static ALCenum CaptureWrapper_captureSamples(CaptureWrapper *self, void *buffer, ALCuint samples);
-static ALCuint CaptureWrapper_availableSamples(CaptureWrapper *self);
-static DECLARE_FORWARD(CaptureWrapper, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(CaptureWrapper, ALCbackend, void, lock)
-static DECLARE_FORWARD(CaptureWrapper, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(CaptureWrapper)
-DEFINE_ALCBACKEND_VTABLE(CaptureWrapper);
-
-
-static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data;
-    int card, dev;
-    int format=-1;
-    int status;
-
-    data=(qsa_data*)calloc(1, sizeof(qsa_data));
-    if (data==NULL)
-    {
-        return ALC_OUT_OF_MEMORY;
-    }
-
-    if(!deviceName)
-        deviceName = qsaDevice;
-
-    if(strcmp(deviceName, qsaDevice) == 0)
-        status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
-    else
-    {
-        const DevMap *iter;
-
-        if(VECTOR_SIZE(CaptureNameMap) == 0)
-            deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
-
-#define MATCH_DEVNAME(iter) ((iter)->name && strcmp(deviceName, (iter)->name)==0)
-        VECTOR_FIND_IF(iter, const DevMap, CaptureNameMap, MATCH_DEVNAME);
-#undef MATCH_DEVNAME
-        if(iter == VECTOR_END(CaptureNameMap))
-        {
-            free(data);
-            return ALC_INVALID_DEVICE;
-        }
-
-        status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE);
-    }
-
-    if(status < 0)
-    {
-        free(data);
-        return ALC_INVALID_DEVICE;
-    }
-
-    data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE);
-    if(data->audio_fd < 0)
-    {
-        snd_pcm_close(data->pcmHandle);
-        free(data);
-        return ALC_INVALID_DEVICE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, deviceName);
-    self->ExtraData = data;
-
-    switch (device->FmtType)
-    {
-        case DevFmtByte:
-             format=SND_PCM_SFMT_S8;
-             break;
-        case DevFmtUByte:
-             format=SND_PCM_SFMT_U8;
-             break;
-        case DevFmtShort:
-             format=SND_PCM_SFMT_S16_LE;
-             break;
-        case DevFmtUShort:
-             format=SND_PCM_SFMT_U16_LE;
-             break;
-        case DevFmtInt:
-             format=SND_PCM_SFMT_S32_LE;
-             break;
-        case DevFmtUInt:
-             format=SND_PCM_SFMT_U32_LE;
-             break;
-        case DevFmtFloat:
-             format=SND_PCM_SFMT_FLOAT_LE;
-             break;
-    }
-
-    /* we actually don't want to block on reads */
-    snd_pcm_nonblock_mode(data->pcmHandle, 1);
-    /* Disable mmap to control data transfer to the audio device */
-    snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
-
-    /* configure a sound channel */
-    memset(&data->cparams, 0, sizeof(data->cparams));
-    data->cparams.mode=SND_PCM_MODE_BLOCK;
-    data->cparams.channel=SND_PCM_CHANNEL_CAPTURE;
-    data->cparams.start_mode=SND_PCM_START_GO;
-    data->cparams.stop_mode=SND_PCM_STOP_STOP;
-
-    data->cparams.buf.block.frag_size=device->UpdateSize*
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    data->cparams.buf.block.frags_max=device->NumUpdates;
-    data->cparams.buf.block.frags_min=device->NumUpdates;
-
-    data->cparams.format.interleave=1;
-    data->cparams.format.rate=device->Frequency;
-    data->cparams.format.voices=ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    data->cparams.format.format=format;
-
-    if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0)
-    {
-        snd_pcm_close(data->pcmHandle);
-        free(data);
-
-        return ALC_INVALID_VALUE;
-    }
-
-    return ALC_NO_ERROR;
-}
-
-static void qsa_close_capture(CaptureWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-
-    if (data->pcmHandle!=NULL)
-        snd_pcm_close(data->pcmHandle);
-
-    free(data);
-    self->ExtraData = NULL;
-}
-
-static void qsa_start_capture(CaptureWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-    int rstatus;
-
-    if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
-    {
-        ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
-        return;
-    }
-
-    memset(&data->csetup, 0, sizeof(data->csetup));
-    data->csetup.channel=SND_PCM_CHANNEL_CAPTURE;
-    if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0)
-    {
-        ERR("capture setup failed: %s\n", snd_strerror(rstatus));
-        return;
-    }
-
-    snd_pcm_capture_go(data->pcmHandle);
-}
-
-static void qsa_stop_capture(CaptureWrapper *self)
-{
-    qsa_data *data = self->ExtraData;
-    snd_pcm_capture_flush(data->pcmHandle);
-}
-
-static ALCuint qsa_available_samples(CaptureWrapper *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data = self->ExtraData;
-    snd_pcm_channel_status_t status;
-    ALint frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    ALint free_size;
-    int rstatus;
-
-    memset(&status, 0, sizeof (status));
-    status.channel=SND_PCM_CHANNEL_CAPTURE;
-    snd_pcm_plugin_status(data->pcmHandle, &status);
-    if ((status.status==SND_PCM_STATUS_OVERRUN) ||
-        (status.status==SND_PCM_STATUS_READY))
-    {
-        if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
-        {
-            ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
-            aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus));
-            return 0;
-        }
-
-        snd_pcm_capture_go(data->pcmHandle);
-        return 0;
-    }
-
-    free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags;
-    free_size-=status.free;
-
-    return free_size/frame_size;
-}
-
-static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    qsa_data *data = self->ExtraData;
-    char* read_ptr;
-    snd_pcm_channel_status_t status;
-    fd_set rfds;
-    int selectret;
-    struct timeval timeout;
-    int bytes_read;
-    ALint frame_size=FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    ALint len=samples*frame_size;
-    int rstatus;
-
-    read_ptr=buffer;
-
-    while (len>0)
-    {
-        FD_ZERO(&rfds);
-        FD_SET(data->audio_fd, &rfds);
-        timeout.tv_sec=2;
-        timeout.tv_usec=0;
-
-        /* Select also works like time slice to OS */
-        bytes_read=0;
-        selectret=select(data->audio_fd+1, &rfds, NULL, NULL, &timeout);
-        switch (selectret)
-        {
-            case -1:
-                 aluHandleDisconnect(device, "Failed to check capture samples");
-                 return ALC_INVALID_DEVICE;
-            case 0:
-                 break;
-            default:
-                 if (FD_ISSET(data->audio_fd, &rfds))
-                 {
-                     bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
-                     break;
-                 }
-                 break;
-        }
-
-        if (bytes_read<=0)
-        {
-            if ((errno==EAGAIN) || (errno==EWOULDBLOCK))
-            {
-                continue;
-            }
-
-            memset(&status, 0, sizeof (status));
-            status.channel=SND_PCM_CHANNEL_CAPTURE;
-            snd_pcm_plugin_status(data->pcmHandle, &status);
-
-            /* we need to reinitialize the sound channel if we've overrun the buffer */
-            if ((status.status==SND_PCM_STATUS_OVERRUN) ||
-                (status.status==SND_PCM_STATUS_READY))
-            {
-                if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
-                {
-                    ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
-                    aluHandleDisconnect(device, "Failed capture recovery: %s",
-                                        snd_strerror(rstatus));
-                    return ALC_INVALID_DEVICE;
-                }
-                snd_pcm_capture_go(data->pcmHandle);
-            }
-        }
-        else
-        {
-            read_ptr+=bytes_read;
-            len-=bytes_read;
-        }
-    }
-
-    return ALC_NO_ERROR;
-}
-
-
-static void CaptureWrapper_Construct(CaptureWrapper *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(CaptureWrapper, ALCbackend, self);
-
-    self->ExtraData = NULL;
-}
-
-static void CaptureWrapper_Destruct(CaptureWrapper *self)
-{
-    if(self->ExtraData)
-        qsa_close_capture(self);
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static ALCenum CaptureWrapper_open(CaptureWrapper *self, const ALCchar *name)
-{
-    return qsa_open_capture(self, name);
-}
-
-static ALCboolean CaptureWrapper_start(CaptureWrapper *self)
-{
-    qsa_start_capture(self);
-    return ALC_TRUE;
-}
-
-static void CaptureWrapper_stop(CaptureWrapper *self)
-{
-    qsa_stop_capture(self);
-}
-
-static ALCenum CaptureWrapper_captureSamples(CaptureWrapper *self, void *buffer, ALCuint samples)
-{
-    return qsa_capture_samples(self, buffer, samples);
-}
-
-static ALCuint CaptureWrapper_availableSamples(CaptureWrapper *self)
-{
-    return qsa_available_samples(self);
-}
-
-
-typedef struct ALCqsaBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCqsaBackendFactory;
-#define ALCQSABACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCqsaBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCqsaBackendFactory_init(ALCqsaBackendFactory* UNUSED(self));
-static void ALCqsaBackendFactory_deinit(ALCqsaBackendFactory* UNUSED(self));
-static ALCboolean ALCqsaBackendFactory_querySupport(ALCqsaBackendFactory* UNUSED(self), ALCbackend_Type type);
-static void ALCqsaBackendFactory_probe(ALCqsaBackendFactory* UNUSED(self), enum DevProbe type);
-static ALCbackend* ALCqsaBackendFactory_createBackend(ALCqsaBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCqsaBackendFactory);
-
-static ALCboolean ALCqsaBackendFactory_init(ALCqsaBackendFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static void ALCqsaBackendFactory_deinit(ALCqsaBackendFactory* UNUSED(self))
-{
-#define FREE_NAME(iter) free((iter)->name)
-    VECTOR_FOR_EACH(DevMap, DeviceNameMap, FREE_NAME);
-    VECTOR_DEINIT(DeviceNameMap);
-
-    VECTOR_FOR_EACH(DevMap, CaptureNameMap, FREE_NAME);
-    VECTOR_DEINIT(CaptureNameMap);
-#undef FREE_NAME
-}
-
-static ALCboolean ALCqsaBackendFactory_querySupport(ALCqsaBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCqsaBackendFactory_probe(ALCqsaBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch (type)
-    {
-        case ALL_DEVICE_PROBE:
-#define FREE_NAME(iter) free((iter)->name)
-            VECTOR_FOR_EACH(DevMap, DeviceNameMap, FREE_NAME);
-            VECTOR_RESIZE(DeviceNameMap, 0, 0);
-#undef FREE_NAME
-
-            deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
-#define APPEND_DEVICE(iter) AppendAllDevicesList((iter)->name)
-            VECTOR_FOR_EACH(const DevMap, DeviceNameMap, APPEND_DEVICE);
-#undef APPEND_DEVICE
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-#define FREE_NAME(iter) free((iter)->name)
-            VECTOR_FOR_EACH(DevMap, CaptureNameMap, FREE_NAME);
-            VECTOR_RESIZE(CaptureNameMap, 0, 0);
-#undef FREE_NAME
-
-            deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
-#define APPEND_DEVICE(iter) AppendCaptureDeviceList((iter)->name)
-            VECTOR_FOR_EACH(const DevMap, CaptureNameMap, APPEND_DEVICE);
-#undef APPEND_DEVICE
-            break;
-    }
-}
-
-static ALCbackend* ALCqsaBackendFactory_createBackend(ALCqsaBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        PlaybackWrapper *backend;
-        NEW_OBJ(backend, PlaybackWrapper)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        CaptureWrapper *backend;
-        NEW_OBJ(backend, CaptureWrapper)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-ALCbackendFactory *ALCqsaBackendFactory_getFactory(void)
-{
-    static ALCqsaBackendFactory factory = ALCQSABACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 0 - 287
Engine/lib/openal-soft/Alc/backends/sdl2.c

@@ -1,287 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2018 by authors.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <SDL2/SDL.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-
-#ifdef _WIN32
-#define DEVNAME_PREFIX "OpenAL Soft on "
-#else
-#define DEVNAME_PREFIX ""
-#endif
-
-typedef struct ALCsdl2Backend {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    SDL_AudioDeviceID deviceID;
-    ALsizei frameSize;
-
-    ALuint Frequency;
-    enum DevFmtChannels FmtChans;
-    enum DevFmtType     FmtType;
-    ALuint UpdateSize;
-} ALCsdl2Backend;
-
-static void ALCsdl2Backend_Construct(ALCsdl2Backend *self, ALCdevice *device);
-static void ALCsdl2Backend_Destruct(ALCsdl2Backend *self);
-static ALCenum ALCsdl2Backend_open(ALCsdl2Backend *self, const ALCchar *name);
-static ALCboolean ALCsdl2Backend_reset(ALCsdl2Backend *self);
-static ALCboolean ALCsdl2Backend_start(ALCsdl2Backend *self);
-static void ALCsdl2Backend_stop(ALCsdl2Backend *self);
-static DECLARE_FORWARD2(ALCsdl2Backend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCsdl2Backend, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCsdl2Backend, ALCbackend, ClockLatency, getClockLatency)
-static void ALCsdl2Backend_lock(ALCsdl2Backend *self);
-static void ALCsdl2Backend_unlock(ALCsdl2Backend *self);
-DECLARE_DEFAULT_ALLOCATORS(ALCsdl2Backend)
-
-DEFINE_ALCBACKEND_VTABLE(ALCsdl2Backend);
-
-static const ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
-
-static void ALCsdl2Backend_Construct(ALCsdl2Backend *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCsdl2Backend, ALCbackend, self);
-
-    self->deviceID = 0;
-    self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    self->Frequency = device->Frequency;
-    self->FmtChans = device->FmtChans;
-    self->FmtType = device->FmtType;
-    self->UpdateSize = device->UpdateSize;
-}
-
-static void ALCsdl2Backend_Destruct(ALCsdl2Backend *self)
-{
-    if(self->deviceID)
-        SDL_CloseAudioDevice(self->deviceID);
-    self->deviceID = 0;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static void ALCsdl2Backend_audioCallback(void *ptr, Uint8 *stream, int len)
-{
-    ALCsdl2Backend *self = (ALCsdl2Backend*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    assert((len % self->frameSize) == 0);
-    aluMixData(device, stream, len / self->frameSize);
-}
-
-static ALCenum ALCsdl2Backend_open(ALCsdl2Backend *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    SDL_AudioSpec want, have;
-
-    SDL_zero(want);
-    SDL_zero(have);
-
-    want.freq = device->Frequency;
-    switch(device->FmtType)
-    {
-        case DevFmtUByte: want.format = AUDIO_U8; break;
-        case DevFmtByte: want.format = AUDIO_S8; break;
-        case DevFmtUShort: want.format = AUDIO_U16SYS; break;
-        case DevFmtShort: want.format = AUDIO_S16SYS; break;
-        case DevFmtUInt: /* fall-through */
-        case DevFmtInt: want.format = AUDIO_S32SYS; break;
-        case DevFmtFloat: want.format = AUDIO_F32; break;
-    }
-    want.channels = (device->FmtChans == DevFmtMono) ? 1 : 2;
-    want.samples = device->UpdateSize;
-    want.callback = ALCsdl2Backend_audioCallback;
-    want.userdata = self;
-
-    /* Passing NULL to SDL_OpenAudioDevice opens a default, which isn't
-     * necessarily the first in the list.
-     */
-    if(!name || strcmp(name, defaultDeviceName) == 0)
-        self->deviceID = SDL_OpenAudioDevice(NULL, 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)
-            self->deviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
-                                                 SDL_AUDIO_ALLOW_ANY_CHANGE);
-        else
-            self->deviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
-                                                 SDL_AUDIO_ALLOW_ANY_CHANGE);
-    }
-    if(self->deviceID == 0)
-        return ALC_INVALID_VALUE;
-
-    device->Frequency = have.freq;
-    if(have.channels == 1)
-        device->FmtChans = DevFmtMono;
-    else if(have.channels == 2)
-        device->FmtChans = DevFmtStereo;
-    else
-    {
-        ERR("Got unhandled SDL channel count: %d\n", (int)have.channels);
-        return ALC_INVALID_VALUE;
-    }
-    switch(have.format)
-    {
-        case AUDIO_U8:     device->FmtType = DevFmtUByte;  break;
-        case AUDIO_S8:     device->FmtType = DevFmtByte;   break;
-        case AUDIO_U16SYS: device->FmtType = DevFmtUShort; break;
-        case AUDIO_S16SYS: device->FmtType = DevFmtShort;  break;
-        case AUDIO_S32SYS: device->FmtType = DevFmtInt;    break;
-        case AUDIO_F32SYS: device->FmtType = DevFmtFloat;  break;
-        default:
-            ERR("Got unsupported SDL format: 0x%04x\n", have.format);
-            return ALC_INVALID_VALUE;
-    }
-    device->UpdateSize = have.samples;
-    device->NumUpdates = 2; /* SDL always (tries to) use two periods. */
-
-    self->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-    self->Frequency = device->Frequency;
-    self->FmtChans = device->FmtChans;
-    self->FmtType = device->FmtType;
-    self->UpdateSize = device->UpdateSize;
-
-    alstr_copy_cstr(&device->DeviceName, name ? name : defaultDeviceName);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCsdl2Backend_reset(ALCsdl2Backend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    device->Frequency = self->Frequency;
-    device->FmtChans = self->FmtChans;
-    device->FmtType = self->FmtType;
-    device->UpdateSize = self->UpdateSize;
-    device->NumUpdates = 2;
-    SetDefaultWFXChannelOrder(device);
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCsdl2Backend_start(ALCsdl2Backend *self)
-{
-    SDL_PauseAudioDevice(self->deviceID, 0);
-    return ALC_TRUE;
-}
-
-static void ALCsdl2Backend_stop(ALCsdl2Backend *self)
-{
-    SDL_PauseAudioDevice(self->deviceID, 1);
-}
-
-static void ALCsdl2Backend_lock(ALCsdl2Backend *self)
-{
-    SDL_LockAudioDevice(self->deviceID);
-}
-
-static void ALCsdl2Backend_unlock(ALCsdl2Backend *self)
-{
-    SDL_UnlockAudioDevice(self->deviceID);
-}
-
-
-typedef struct ALCsdl2BackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCsdl2BackendFactory;
-#define ALCsdl2BACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCsdl2BackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void);
-
-static ALCboolean ALCsdl2BackendFactory_init(ALCsdl2BackendFactory *self);
-static void ALCsdl2BackendFactory_deinit(ALCsdl2BackendFactory *self);
-static ALCboolean ALCsdl2BackendFactory_querySupport(ALCsdl2BackendFactory *self, ALCbackend_Type type);
-static void ALCsdl2BackendFactory_probe(ALCsdl2BackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCsdl2BackendFactory_createBackend(ALCsdl2BackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCsdl2BackendFactory);
-
-
-ALCbackendFactory *ALCsdl2BackendFactory_getFactory(void)
-{
-    static ALCsdl2BackendFactory factory = ALCsdl2BACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCsdl2BackendFactory_init(ALCsdl2BackendFactory* UNUSED(self))
-{
-    if(SDL_InitSubSystem(SDL_INIT_AUDIO) == 0)
-        return AL_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCsdl2BackendFactory_deinit(ALCsdl2BackendFactory* UNUSED(self))
-{
-    SDL_QuitSubSystem(SDL_INIT_AUDIO);
-}
-
-static ALCboolean ALCsdl2BackendFactory_querySupport(ALCsdl2BackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCsdl2BackendFactory_probe(ALCsdl2BackendFactory* UNUSED(self), enum DevProbe type)
-{
-    int num_devices, i;
-    al_string name;
-
-    if(type != ALL_DEVICE_PROBE)
-        return;
-
-    AL_STRING_INIT(name);
-    num_devices = SDL_GetNumAudioDevices(SDL_FALSE);
-
-    AppendAllDevicesList(defaultDeviceName);
-    for(i = 0;i < num_devices;++i)
-    {
-        alstr_copy_cstr(&name, DEVNAME_PREFIX);
-        alstr_append_cstr(&name, SDL_GetAudioDeviceName(i, SDL_FALSE));
-        AppendAllDevicesList(alstr_get_cstr(name));
-    }
-    alstr_reset(&name);
-}
-
-static ALCbackend* ALCsdl2BackendFactory_createBackend(ALCsdl2BackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCsdl2Backend *backend;
-        NEW_OBJ(backend, ALCsdl2Backend)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 216 - 0
Engine/lib/openal-soft/Alc/backends/sdl2.cpp

@@ -0,0 +1,216 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by authors.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "backends/sdl2.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alu.h"
+#include "core/logging.h"
+
+#include <SDL2/SDL.h>
+
+
+namespace {
+
+#ifdef _WIN32
+#define DEVNAME_PREFIX "OpenAL Soft on "
+#else
+#define DEVNAME_PREFIX ""
+#endif
+
+constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
+
+struct Sdl2Backend final : public BackendBase {
+    Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~Sdl2Backend() override;
+
+    void audioCallback(Uint8 *stream, int len) noexcept;
+    static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
+    { static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    SDL_AudioDeviceID mDeviceID{0u};
+    uint mFrameSize{0};
+
+    uint mFrequency{0u};
+    DevFmtChannels mFmtChans{};
+    DevFmtType     mFmtType{};
+    uint mUpdateSize{0u};
+
+    DEF_NEWDEL(Sdl2Backend)
+};
+
+Sdl2Backend::~Sdl2Backend()
+{
+    if(mDeviceID)
+        SDL_CloseAudioDevice(mDeviceID);
+    mDeviceID = 0;
+}
+
+void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
+{
+    const auto ulen = static_cast<unsigned int>(len);
+    assert((ulen % mFrameSize) == 0);
+    mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
+}
+
+void Sdl2Backend::open(const char *name)
+{
+    SDL_AudioSpec want{}, have{};
+
+    want.freq = static_cast<int>(mDevice->Frequency);
+    switch(mDevice->FmtType)
+    {
+    case DevFmtUByte: want.format = AUDIO_U8; break;
+    case DevFmtByte: want.format = AUDIO_S8; break;
+    case DevFmtUShort: want.format = AUDIO_U16SYS; break;
+    case DevFmtShort: want.format = AUDIO_S16SYS; break;
+    case DevFmtUInt: /* fall-through */
+    case DevFmtInt: want.format = AUDIO_S32SYS; break;
+    case DevFmtFloat: want.format = AUDIO_F32; break;
+    }
+    want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
+    want.samples = static_cast<Uint16>(mDevice->UpdateSize);
+    want.callback = &Sdl2Backend::audioCallbackC;
+    want.userdata = this;
+
+    /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
+     * necessarily the first in the list.
+     */
+    if(!name || strcmp(name, defaultDeviceName) == 0)
+        mDeviceID = 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,
+                SDL_AUDIO_ALLOW_ANY_CHANGE);
+        else
+            mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
+                SDL_AUDIO_ALLOW_ANY_CHANGE);
+    }
+    if(mDeviceID == 0)
+        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;
+    else
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Unhandled SDL channel count: %d", int{have.channels}};
+
+    switch(have.format)
+    {
+    case AUDIO_U8:     mDevice->FmtType = DevFmtUByte;  break;
+    case AUDIO_S8:     mDevice->FmtType = DevFmtByte;   break;
+    case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
+    case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort;  break;
+    case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt;    break;
+    case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat;  break;
+    default:
+        throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 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;
+
+    mDevice->DeviceName = name ? name : defaultDeviceName;
+}
+
+bool Sdl2Backend::reset()
+{
+    mDevice->Frequency = mFrequency;
+    mDevice->FmtChans = mFmtChans;
+    mDevice->FmtType = mFmtType;
+    mDevice->UpdateSize = mUpdateSize;
+    mDevice->BufferSize = mUpdateSize * 2;
+    setDefaultWFXChannelOrder();
+    return true;
+}
+
+void Sdl2Backend::start()
+{ SDL_PauseAudioDevice(mDeviceID, 0); }
+
+void Sdl2Backend::stop()
+{ SDL_PauseAudioDevice(mDeviceID, 1); }
+
+} // namespace
+
+BackendFactory &SDL2BackendFactory::getFactory()
+{
+    static SDL2BackendFactory factory{};
+    return factory;
+}
+
+bool SDL2BackendFactory::init()
+{ return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); }
+
+bool SDL2BackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback; }
+
+std::string SDL2BackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+
+    if(type != BackendType::Playback)
+        return outnames;
+
+    int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
+
+    /* Includes null char. */
+    outnames.append(defaultDeviceName, sizeof(defaultDeviceName));
+    for(int i{0};i < num_devices;++i)
+    {
+        std::string name{DEVNAME_PREFIX};
+        name += SDL_GetAudioDeviceName(i, SDL_FALSE);
+        if(!name.empty())
+            outnames.append(name.c_str(), name.length()+1);
+    }
+    return outnames;
+}
+
+BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new Sdl2Backend{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/sdl2.h

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

+ 0 - 342
Engine/lib/openal-soft/Alc/backends/sndio.c

@@ -1,342 +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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "threads.h"
-
-#include "backends/base.h"
-
-#include <sndio.h>
-
-
-
-
-typedef struct ALCsndioBackend {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    struct sio_hdl *sndHandle;
-
-    ALvoid *mix_data;
-    ALsizei data_size;
-
-    ATOMIC(int) killNow;
-    althrd_t thread;
-} ALCsndioBackend;
-
-static int ALCsndioBackend_mixerProc(void *ptr);
-
-static void ALCsndioBackend_Construct(ALCsndioBackend *self, ALCdevice *device);
-static void ALCsndioBackend_Destruct(ALCsndioBackend *self);
-static ALCenum ALCsndioBackend_open(ALCsndioBackend *self, const ALCchar *name);
-static ALCboolean ALCsndioBackend_reset(ALCsndioBackend *self);
-static ALCboolean ALCsndioBackend_start(ALCsndioBackend *self);
-static void ALCsndioBackend_stop(ALCsndioBackend *self);
-static DECLARE_FORWARD2(ALCsndioBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCsndioBackend, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCsndioBackend, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCsndioBackend, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCsndioBackend, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCsndioBackend)
-
-DEFINE_ALCBACKEND_VTABLE(ALCsndioBackend);
-
-
-static const ALCchar sndio_device[] = "SndIO Default";
-
-
-static void ALCsndioBackend_Construct(ALCsndioBackend *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCsndioBackend, ALCbackend, self);
-
-    self->sndHandle = NULL;
-    self->mix_data = NULL;
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCsndioBackend_Destruct(ALCsndioBackend *self)
-{
-    if(self->sndHandle)
-        sio_close(self->sndHandle);
-    self->sndHandle = NULL;
-
-    al_free(self->mix_data);
-    self->mix_data = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCsndioBackend_mixerProc(void *ptr)
-{
-    ALCsndioBackend *self = (ALCsndioBackend*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALsizei frameSize;
-    size_t wrote;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        ALsizei len = self->data_size;
-        ALubyte *WritePtr = self->mix_data;
-
-        ALCsndioBackend_lock(self);
-        aluMixData(device, WritePtr, len/frameSize);
-        ALCsndioBackend_unlock(self);
-        while(len > 0 && !ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
-        {
-            wrote = sio_write(self->sndHandle, WritePtr, len);
-            if(wrote == 0)
-            {
-                ERR("sio_write failed\n");
-                ALCdevice_Lock(device);
-                aluHandleDisconnect(device, "Failed to write playback samples");
-                ALCdevice_Unlock(device);
-                break;
-            }
-
-            len -= wrote;
-            WritePtr += wrote;
-        }
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCsndioBackend_open(ALCsndioBackend *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-
-    if(!name)
-        name = sndio_device;
-    else if(strcmp(name, sndio_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    self->sndHandle = sio_open(NULL, SIO_PLAY, 0);
-    if(self->sndHandle == NULL)
-    {
-        ERR("Could not open device\n");
-        return ALC_INVALID_VALUE;
-    }
-
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCsndioBackend_reset(ALCsndioBackend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    struct sio_par par;
-
-    sio_initpar(&par);
-
-    par.rate = device->Frequency;
-    par.pchan = ((device->FmtChans != DevFmtMono) ? 2 : 1);
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            par.bits = 8;
-            par.sig = 1;
-            break;
-        case DevFmtUByte:
-            par.bits = 8;
-            par.sig = 0;
-            break;
-        case DevFmtFloat:
-        case DevFmtShort:
-            par.bits = 16;
-            par.sig = 1;
-            break;
-        case DevFmtUShort:
-            par.bits = 16;
-            par.sig = 0;
-            break;
-        case DevFmtInt:
-            par.bits = 32;
-            par.sig = 1;
-            break;
-        case DevFmtUInt:
-            par.bits = 32;
-            par.sig = 0;
-            break;
-    }
-    par.le = SIO_LE_NATIVE;
-
-    par.round = device->UpdateSize;
-    par.appbufsz = device->UpdateSize * (device->NumUpdates-1);
-    if(!par.appbufsz) par.appbufsz = device->UpdateSize;
-
-    if(!sio_setpar(self->sndHandle, &par) || !sio_getpar(self->sndHandle, &par))
-    {
-        ERR("Failed to set device parameters\n");
-        return ALC_FALSE;
-    }
-
-    if(par.bits != par.bps*8)
-    {
-        ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
-        return ALC_FALSE;
-    }
-
-    device->Frequency = par.rate;
-    device->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
-
-    if(par.bits == 8 && par.sig == 1)
-        device->FmtType = DevFmtByte;
-    else if(par.bits == 8 && par.sig == 0)
-        device->FmtType = DevFmtUByte;
-    else if(par.bits == 16 && par.sig == 1)
-        device->FmtType = DevFmtShort;
-    else if(par.bits == 16 && par.sig == 0)
-        device->FmtType = DevFmtUShort;
-    else if(par.bits == 32 && par.sig == 1)
-        device->FmtType = DevFmtInt;
-    else if(par.bits == 32 && par.sig == 0)
-        device->FmtType = DevFmtUInt;
-    else
-    {
-        ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
-        return ALC_FALSE;
-    }
-
-    device->UpdateSize = par.round;
-    device->NumUpdates = (par.bufsz/par.round) + 1;
-
-    SetDefaultChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCsndioBackend_start(ALCsndioBackend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-
-    self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
-        device->FmtChans, device->FmtType, device->AmbiOrder
-    );
-    al_free(self->mix_data);
-    self->mix_data = al_calloc(16, self->data_size);
-
-    if(!sio_start(self->sndHandle))
-    {
-        ERR("Error starting playback\n");
-        return ALC_FALSE;
-    }
-
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCsndioBackend_mixerProc, self) != althrd_success)
-    {
-        sio_stop(self->sndHandle);
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCsndioBackend_stop(ALCsndioBackend *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &res);
-
-    if(!sio_stop(self->sndHandle))
-        ERR("Error stopping device\n");
-
-    al_free(self->mix_data);
-    self->mix_data = NULL;
-}
-
-
-typedef struct ALCsndioBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCsndioBackendFactory;
-#define ALCSNDIOBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCsndioBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCsndioBackendFactory_getFactory(void);
-
-static ALCboolean ALCsndioBackendFactory_init(ALCsndioBackendFactory *self);
-static DECLARE_FORWARD(ALCsndioBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCsndioBackendFactory_querySupport(ALCsndioBackendFactory *self, ALCbackend_Type type);
-static void ALCsndioBackendFactory_probe(ALCsndioBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCsndioBackendFactory_createBackend(ALCsndioBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCsndioBackendFactory);
-
-
-ALCbackendFactory *ALCsndioBackendFactory_getFactory(void)
-{
-    static ALCsndioBackendFactory factory = ALCSNDIOBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCsndioBackendFactory_init(ALCsndioBackendFactory* UNUSED(self))
-{
-    /* No dynamic loading */
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCsndioBackendFactory_querySupport(ALCsndioBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCsndioBackendFactory_probe(ALCsndioBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(sndio_device);
-            break;
-        case CAPTURE_DEVICE_PROBE:
-            break;
-    }
-}
-
-static ALCbackend* ALCsndioBackendFactory_createBackend(ALCsndioBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCsndioBackend *backend;
-        NEW_OBJ(backend, ALCsndioBackend)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 517 - 0
Engine/lib/openal-soft/Alc/backends/sndio.cpp

@@ -0,0 +1,517 @@
+/**
+ * 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 "backends/sndio.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <thread>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "vector.h"
+
+#include <sndio.h>
+
+
+namespace {
+
+static const char sndio_device[] = "SndIO Default";
+
+
+struct SndioPlayback final : public BackendBase {
+    SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~SndioPlayback() override;
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    sio_hdl *mSndHandle{nullptr};
+
+    al::vector<al::byte> mBuffer;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(SndioPlayback)
+};
+
+SndioPlayback::~SndioPlayback()
+{
+    if(mSndHandle)
+        sio_close(mSndHandle);
+    mSndHandle = nullptr;
+}
+
+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};
+
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    while(!mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
+    {
+        al::byte *WritePtr{mBuffer.data()};
+        size_t len{mBuffer.size()};
+
+        mDevice->renderSamples(WritePtr, static_cast<uint>(len/frameSize), frameStep);
+        while(len > 0 && !mKillNow.load(std::memory_order_acquire))
+        {
+            size_t wrote{sio_write(mSndHandle, WritePtr, len)};
+            if(wrote == 0)
+            {
+                ERR("sio_write failed\n");
+                mDevice->handleDisconnect("Failed to write playback samples");
+                break;
+            }
+
+            len -= wrote;
+            WritePtr += wrote;
+        }
+    }
+
+    return 0;
+}
+
+
+void SndioPlayback::open(const char *name)
+{
+    if(!name)
+        name = sndio_device;
+    else if(strcmp(name, sndio_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
+    if(mSndHandle == nullptr)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
+
+    mDevice->DeviceName = name;
+}
+
+bool SndioPlayback::reset()
+{
+    sio_par par;
+    sio_initpar(&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)
+    {
+    case DevFmtByte:
+        par.bits = 8;
+        par.sig = 1;
+        break;
+    case DevFmtUByte:
+        par.bits = 8;
+        par.sig = 0;
+        break;
+    case DevFmtFloat:
+    case DevFmtShort:
+        par.bits = 16;
+        par.sig = 1;
+        break;
+    case DevFmtUShort:
+        par.bits = 16;
+        par.sig = 0;
+        break;
+    case DevFmtInt:
+        par.bits = 32;
+        par.sig = 1;
+        break;
+    case DevFmtUInt:
+        par.bits = 32;
+        par.sig = 0;
+        break;
+    }
+    par.le = SIO_LE_NATIVE;
+
+    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;
+        }
+    }
+    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;
+    }
+
+    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;
+    else
+    {
+        ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
+        return false;
+    }
+
+    setDefaultChannelOrder();
+
+    mDevice->UpdateSize = par.round;
+    mDevice->BufferSize = par.bufsz + par.round;
+
+    mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
+    if(par.sig == 1)
+        std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
+    else if(par.bits == 8)
+        std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
+    else if(par.bits == 16)
+        std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
+    else if(par.bits == 32)
+        std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u);
+
+    return true;
+}
+
+void SndioPlayback::start()
+{
+    if(!sio_start(mSndHandle))
+        throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"};
+
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        sio_stop(mSndHandle);
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void SndioPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(!sio_stop(mSndHandle))
+        ERR("Error stopping device\n");
+}
+
+
+struct SndioCapture final : public BackendBase {
+    SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~SndioCapture() override;
+
+    int recordProc();
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    sio_hdl *mSndHandle{nullptr};
+
+    RingBufferPtr mRing;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(SndioCapture)
+};
+
+SndioCapture::~SndioCapture()
+{
+    if(mSndHandle)
+        sio_close(mSndHandle);
+    mSndHandle = nullptr;
+}
+
+int SndioCapture::recordProc()
+{
+    SetRTPriority();
+    althrd_setname(RECORD_THREAD_NAME);
+
+    const uint frameSize{mDevice->frameSizeFromFmt()};
+
+    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)
+        {
+            static char junk[4096];
+            sio_read(mSndHandle, junk,
+                minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
+            continue;
+        }
+
+        size_t total{0u};
+        data.first.len  *= frameSize;
+        data.second.len *= frameSize;
+        todo = minz(todo, mDevice->UpdateSize) * frameSize;
+        while(total < todo)
+        {
+            if(!data.first.len)
+                data.first = data.second;
+
+            size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
+            if(!got)
+            {
+                mDevice->handleDisconnect("Failed to read capture samples");
+                break;
+            }
+
+            data.first.buf += got;
+            data.first.len -= got;
+            total += got;
+        }
+        mRing->writeAdvance(total / frameSize);
+    }
+
+    return 0;
+}
+
+
+void SndioCapture::open(const char *name)
+{
+    if(!name)
+        name = sndio_device;
+    else if(strcmp(name, sndio_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    mSndHandle = sio_open(nullptr, SIO_REC, 0);
+    if(mSndHandle == nullptr)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
+
+    sio_par par;
+    sio_initpar(&par);
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        par.bps = 1;
+        par.sig = 1;
+        break;
+    case DevFmtUByte:
+        par.bps = 1;
+        par.sig = 0;
+        break;
+    case DevFmtShort:
+        par.bps = 2;
+        par.sig = 1;
+        break;
+    case DevFmtUShort:
+        par.bps = 2;
+        par.sig = 0;
+        break;
+    case DevFmtInt:
+        par.bps = 4;
+        par.sig = 1;
+        break;
+    case DevFmtUInt:
+        par.bps = 4;
+        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.le = SIO_LE_NATIVE;
+    par.msb = SIO_LE_NATIVE ? 0 : 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;
+
+    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)
+        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)
+        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};
+
+    mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
+
+    setDefaultChannelOrder();
+
+    mDevice->DeviceName = name;
+}
+
+void SndioCapture::start()
+{
+    if(!sio_start(mSndHandle))
+        throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"};
+
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
+    }
+    catch(std::exception& e) {
+        sio_stop(mSndHandle);
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start capture thread: %s", e.what()};
+    }
+}
+
+void SndioCapture::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(!sio_stop(mSndHandle))
+        ERR("Error stopping device\n");
+}
+
+void SndioCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+uint SndioCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+} // namespace
+
+BackendFactory &SndIOBackendFactory::getFactory()
+{
+    static SndIOBackendFactory factory{};
+    return factory;
+}
+
+bool SndIOBackendFactory::init()
+{ return true; }
+
+bool SndIOBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+std::string SndIOBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+    case BackendType::Capture:
+        /* Includes null char. */
+        outnames.append(sndio_device, sizeof(sndio_device));
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new SndioPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new SndioCapture{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/sndio.h

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

+ 0 - 360
Engine/lib/openal-soft/Alc/backends/solaris.c

@@ -1,360 +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 <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-#include <unistd.h>
-#include <errno.h>
-#include <math.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-#include <sys/audioio.h>
-
-
-typedef struct ALCsolarisBackend {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    int fd;
-
-    ALubyte *mix_data;
-    int data_size;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCsolarisBackend;
-
-static int ALCsolarisBackend_mixerProc(void *ptr);
-
-static void ALCsolarisBackend_Construct(ALCsolarisBackend *self, ALCdevice *device);
-static void ALCsolarisBackend_Destruct(ALCsolarisBackend *self);
-static ALCenum ALCsolarisBackend_open(ALCsolarisBackend *self, const ALCchar *name);
-static ALCboolean ALCsolarisBackend_reset(ALCsolarisBackend *self);
-static ALCboolean ALCsolarisBackend_start(ALCsolarisBackend *self);
-static void ALCsolarisBackend_stop(ALCsolarisBackend *self);
-static DECLARE_FORWARD2(ALCsolarisBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCsolarisBackend, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCsolarisBackend)
-
-DEFINE_ALCBACKEND_VTABLE(ALCsolarisBackend);
-
-
-static const ALCchar solaris_device[] = "Solaris Default";
-
-static const char *solaris_driver = "/dev/audio";
-
-
-static void ALCsolarisBackend_Construct(ALCsolarisBackend *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCsolarisBackend, ALCbackend, self);
-
-    self->fd = -1;
-    self->mix_data = NULL;
-    ATOMIC_INIT(&self->killNow, AL_FALSE);
-}
-
-static void ALCsolarisBackend_Destruct(ALCsolarisBackend *self)
-{
-    if(self->fd != -1)
-        close(self->fd);
-    self->fd = -1;
-
-    free(self->mix_data);
-    self->mix_data = NULL;
-    self->data_size = 0;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int ALCsolarisBackend_mixerProc(void *ptr)
-{
-    ALCsolarisBackend *self = ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct timeval timeout;
-    ALubyte *write_ptr;
-    ALint frame_size;
-    ALint to_write;
-    ssize_t wrote;
-    fd_set wfds;
-    int sret;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    ALCsolarisBackend_lock(self);
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        FD_ZERO(&wfds);
-        FD_SET(self->fd, &wfds);
-        timeout.tv_sec = 1;
-        timeout.tv_usec = 0;
-
-        ALCsolarisBackend_unlock(self);
-        sret = select(self->fd+1, NULL, &wfds, NULL, &timeout);
-        ALCsolarisBackend_lock(self);
-        if(sret < 0)
-        {
-            if(errno == EINTR)
-                continue;
-            ERR("select failed: %s\n", strerror(errno));
-            aluHandleDisconnect(device, "Failed to wait for playback buffer: %s", strerror(errno));
-            break;
-        }
-        else if(sret == 0)
-        {
-            WARN("select timeout\n");
-            continue;
-        }
-
-        write_ptr = self->mix_data;
-        to_write = self->data_size;
-        aluMixData(device, write_ptr, to_write/frame_size);
-        while(to_write > 0 && !ATOMIC_LOAD_SEQ(&self->killNow))
-        {
-            wrote = write(self->fd, write_ptr, to_write);
-            if(wrote < 0)
-            {
-                if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-                    continue;
-                ERR("write failed: %s\n", strerror(errno));
-                aluHandleDisconnect(device, "Failed to write playback samples: %s",
-                                    strerror(errno));
-                break;
-            }
-
-            to_write -= wrote;
-            write_ptr += wrote;
-        }
-    }
-    ALCsolarisBackend_unlock(self);
-
-    return 0;
-}
-
-
-static ALCenum ALCsolarisBackend_open(ALCsolarisBackend *self, const ALCchar *name)
-{
-    ALCdevice *device;
-
-    if(!name)
-        name = solaris_device;
-    else if(strcmp(name, solaris_device) != 0)
-        return ALC_INVALID_VALUE;
-
-    self->fd = open(solaris_driver, O_WRONLY);
-    if(self->fd == -1)
-    {
-        ERR("Could not open %s: %s\n", solaris_driver, strerror(errno));
-        return ALC_INVALID_VALUE;
-    }
-
-    device = STATIC_CAST(ALCbackend,self)->mDevice;
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCsolarisBackend_reset(ALCsolarisBackend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-    audio_info_t info;
-    ALsizei frameSize;
-    ALsizei numChannels;
-
-    AUDIO_INITINFO(&info);
-
-    info.play.sample_rate = device->Frequency;
-
-    if(device->FmtChans != DevFmtMono)
-        device->FmtChans = DevFmtStereo;
-    numChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    info.play.channels = numChannels;
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            info.play.precision = 8;
-            info.play.encoding = AUDIO_ENCODING_LINEAR;
-            break;
-        case DevFmtUByte:
-            info.play.precision = 8;
-            info.play.encoding = AUDIO_ENCODING_LINEAR8;
-            break;
-        case DevFmtUShort:
-        case DevFmtInt:
-        case DevFmtUInt:
-        case DevFmtFloat:
-            device->FmtType = DevFmtShort;
-            /* fall-through */
-        case DevFmtShort:
-            info.play.precision = 16;
-            info.play.encoding = AUDIO_ENCODING_LINEAR;
-            break;
-    }
-
-    frameSize = numChannels * BytesFromDevFmt(device->FmtType);
-    info.play.buffer_size = device->UpdateSize*device->NumUpdates * frameSize;
-
-    if(ioctl(self->fd, AUDIO_SETINFO, &info) < 0)
-    {
-        ERR("ioctl failed: %s\n", strerror(errno));
-        return ALC_FALSE;
-    }
-
-    if(ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != (ALsizei)info.play.channels)
-    {
-        ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(device->FmtChans), info.play.channels);
-        return ALC_FALSE;
-    }
-
-    if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && device->FmtType == DevFmtUByte) ||
-         (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtByte) ||
-         (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtShort) ||
-         (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && device->FmtType == DevFmtInt)))
-    {
-        ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(device->FmtType),
-            info.play.precision, info.play.encoding);
-        return ALC_FALSE;
-    }
-
-    device->Frequency = info.play.sample_rate;
-    device->UpdateSize = (info.play.buffer_size/device->NumUpdates) + 1;
-
-    SetDefaultChannelOrder(device);
-
-    free(self->mix_data);
-    self->data_size = device->UpdateSize * FrameSizeFromDevFmt(
-        device->FmtChans, device->FmtType, device->AmbiOrder
-    );
-    self->mix_data = calloc(1, self->data_size);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCsolarisBackend_start(ALCsolarisBackend *self)
-{
-    ATOMIC_STORE_SEQ(&self->killNow, AL_FALSE);
-    if(althrd_create(&self->thread, ALCsolarisBackend_mixerProc, self) != althrd_success)
-        return ALC_FALSE;
-    return ALC_TRUE;
-}
-
-static void ALCsolarisBackend_stop(ALCsolarisBackend *self)
-{
-    int res;
-
-    if(ATOMIC_EXCHANGE_SEQ(&self->killNow, AL_TRUE))
-        return;
-
-    althrd_join(self->thread, &res);
-
-    if(ioctl(self->fd, AUDIO_DRAIN) < 0)
-        ERR("Error draining device: %s\n", strerror(errno));
-}
-
-
-typedef struct ALCsolarisBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCsolarisBackendFactory;
-#define ALCSOLARISBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCsolarisBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void);
-
-static ALCboolean ALCsolarisBackendFactory_init(ALCsolarisBackendFactory *self);
-static DECLARE_FORWARD(ALCsolarisBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCsolarisBackendFactory_querySupport(ALCsolarisBackendFactory *self, ALCbackend_Type type);
-static void ALCsolarisBackendFactory_probe(ALCsolarisBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCsolarisBackendFactory_createBackend(ALCsolarisBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCsolarisBackendFactory);
-
-
-ALCbackendFactory *ALCsolarisBackendFactory_getFactory(void)
-{
-    static ALCsolarisBackendFactory factory = ALCSOLARISBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCsolarisBackendFactory_init(ALCsolarisBackendFactory* UNUSED(self))
-{
-    ConfigValueStr(NULL, "solaris", "device", &solaris_driver);
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCsolarisBackendFactory_querySupport(ALCsolarisBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCsolarisBackendFactory_probe(ALCsolarisBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-        {
-#ifdef HAVE_STAT
-            struct stat buf;
-            if(stat(solaris_driver, &buf) == 0)
-#endif
-                AppendAllDevicesList(solaris_device);
-        }
-        break;
-
-        case CAPTURE_DEVICE_PROBE:
-            break;
-    }
-}
-
-ALCbackend* ALCsolarisBackendFactory_createBackend(ALCsolarisBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCsolarisBackend *backend;
-        NEW_OBJ(backend, ALCsolarisBackend)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 294 - 0
Engine/lib/openal-soft/Alc/backends/solaris.cpp

@@ -0,0 +1,294 @@
+/**
+ * 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 "backends/solaris.h"
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <math.h>
+
+#include <thread>
+#include <functional>
+
+#include "alcmain.h"
+#include "albyte.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "threads.h"
+#include "vector.h"
+
+#include <sys/audioio.h>
+
+
+namespace {
+
+constexpr char solaris_device[] = "Solaris Default";
+
+std::string solaris_driver{"/dev/audio"};
+
+
+struct SolarisBackend final : public BackendBase {
+    SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~SolarisBackend() override;
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    int mFd{-1};
+
+    al::vector<al::byte> mBuffer;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(SolarisBackend)
+};
+
+SolarisBackend::~SolarisBackend()
+{
+    if(mFd != -1)
+        close(mFd);
+    mFd = -1;
+}
+
+int SolarisBackend::mixerProc()
+{
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    const size_t frame_step{mDevice->channelsFromFmt()};
+    const uint frame_size{mDevice->frameSizeFromFmt()};
+
+    while(!mKillNow.load(std::memory_order_acquire)
+        && mDevice->Connected.load(std::memory_order_acquire))
+    {
+        pollfd pollitem{};
+        pollitem.fd = mFd;
+        pollitem.events = POLLOUT;
+
+        int pret{poll(&pollitem, 1, 1000)};
+        if(pret < 0)
+        {
+            if(errno == EINTR || errno == EAGAIN)
+                continue;
+            ERR("poll failed: %s\n", strerror(errno));
+            mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
+            break;
+        }
+        else if(pret == 0)
+        {
+            WARN("poll timeout\n");
+            continue;
+        }
+
+        al::byte *write_ptr{mBuffer.data()};
+        size_t to_write{mBuffer.size()};
+        mDevice->renderSamples(write_ptr, static_cast<uint>(to_write/frame_size), frame_step);
+        while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
+        {
+            ssize_t wrote{write(mFd, write_ptr, to_write)};
+            if(wrote < 0)
+            {
+                if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+                    continue;
+                ERR("write failed: %s\n", strerror(errno));
+                mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
+                break;
+            }
+
+            to_write -= static_cast<size_t>(wrote);
+            write_ptr += wrote;
+        }
+    }
+
+    return 0;
+}
+
+
+void SolarisBackend::open(const char *name)
+{
+    if(!name)
+        name = solaris_device;
+    else if(strcmp(name, solaris_device) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+    mFd = ::open(solaris_driver.c_str(), O_WRONLY);
+    if(mFd == -1)
+        throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
+            solaris_driver.c_str(), strerror(errno)};
+
+    mDevice->DeviceName = name;
+}
+
+bool SolarisBackend::reset()
+{
+    audio_info_t info;
+    AUDIO_INITINFO(&info);
+
+    info.play.sample_rate = mDevice->Frequency;
+
+    if(mDevice->FmtChans != DevFmtMono)
+        mDevice->FmtChans = DevFmtStereo;
+    uint numChannels{mDevice->channelsFromFmt()};
+    info.play.channels = numChannels;
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        info.play.precision = 8;
+        info.play.encoding = AUDIO_ENCODING_LINEAR;
+        break;
+    case DevFmtUByte:
+        info.play.precision = 8;
+        info.play.encoding = AUDIO_ENCODING_LINEAR8;
+        break;
+    case DevFmtUShort:
+    case DevFmtInt:
+    case DevFmtUInt:
+    case DevFmtFloat:
+        mDevice->FmtType = DevFmtShort;
+        /* fall-through */
+    case DevFmtShort:
+        info.play.precision = 16;
+        info.play.encoding = AUDIO_ENCODING_LINEAR;
+        break;
+    }
+
+    uint frameSize{numChannels * mDevice->bytesFromFmt()};
+    info.play.buffer_size = mDevice->BufferSize * frameSize;
+
+    if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
+    {
+        ERR("ioctl failed: %s\n", strerror(errno));
+        return false;
+    }
+
+    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.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
+         (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
+         (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
+         (info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
+    {
+        ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
+            info.play.precision, info.play.encoding);
+        return false;
+    }
+
+    mDevice->Frequency = info.play.sample_rate;
+    mDevice->BufferSize = info.play.buffer_size / frameSize;
+    mDevice->UpdateSize = mDevice->BufferSize / 2;
+
+    setDefaultChannelOrder();
+
+    mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+    std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
+
+    return true;
+}
+
+void SolarisBackend::start()
+{
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void SolarisBackend::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(ioctl(mFd, AUDIO_DRAIN) < 0)
+        ERR("Error draining device: %s\n", strerror(errno));
+}
+
+} // namespace
+
+BackendFactory &SolarisBackendFactory::getFactory()
+{
+    static SolarisBackendFactory factory{};
+    return factory;
+}
+
+bool SolarisBackendFactory::init()
+{
+    if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
+        solaris_driver = std::move(*devopt);
+    return true;
+}
+
+bool SolarisBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback; }
+
+std::string SolarisBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+    {
+        struct stat buf;
+        if(stat(solaris_driver.c_str(), &buf) == 0)
+            outnames.append(solaris_device, sizeof(solaris_device));
+    }
+    break;
+
+    case BackendType::Capture:
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new SolarisBackend{device}};
+    return nullptr;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/solaris.h

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

+ 0 - 2044
Engine/lib/openal-soft/Alc/backends/wasapi.c

@@ -1,2044 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2011 by authors.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#define COBJMACROS
-#include <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
-#include <wtypes.h>
-#include <mmdeviceapi.h>
-#include <audioclient.h>
-#include <cguid.h>
-#include <devpropdef.h>
-#include <mmreg.h>
-#include <propsys.h>
-#include <propkey.h>
-#include <devpkey.h>
-#ifndef _WAVEFORMATEXTENSIBLE_
-#include <ks.h>
-#include <ksmedia.h>
-#endif
-
-#include "alMain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "threads.h"
-#include "compat.h"
-#include "alstring.h"
-#include "converter.h"
-
-#include "backends/base.h"
-
-
-DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
-
-DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
-DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
-DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
-
-#define MONO SPEAKER_FRONT_CENTER
-#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
-#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
-#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
-#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
-#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
-#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
-#define X7DOT1_WIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_FRONT_LEFT_OF_CENTER|SPEAKER_FRONT_RIGHT_OF_CENTER)
-
-#define REFTIME_PER_SEC ((REFERENCE_TIME)10000000)
-
-#define DEVNAME_HEAD "OpenAL Soft on "
-
-
-/* Scales the given value using 64-bit integer math, ceiling the result. */
-static inline ALuint64 ScaleCeil(ALuint64 val, ALuint64 new_scale, ALuint64 old_scale)
-{
-    return (val*new_scale + old_scale-1) / old_scale;
-}
-
-
-typedef struct {
-    al_string name;
-    al_string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
-    WCHAR *devid;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
-
-static void clear_devlist(vector_DevMap *list)
-{
-#define CLEAR_DEVMAP(i) do {     \
-    AL_STRING_DEINIT((i)->name); \
-    AL_STRING_DEINIT((i)->endpoint_guid); \
-    free((i)->devid);            \
-    (i)->devid = NULL;           \
-} while(0)
-    VECTOR_FOR_EACH(DevMap, *list, CLEAR_DEVMAP);
-    VECTOR_RESIZE(*list, 0, 0);
-#undef CLEAR_DEVMAP
-}
-
-static vector_DevMap PlaybackDevices;
-static vector_DevMap CaptureDevices;
-
-
-static HANDLE ThreadHdl;
-static DWORD ThreadID;
-
-typedef struct {
-    HANDLE FinishedEvt;
-    HRESULT result;
-} ThreadRequest;
-
-#define WM_USER_First       (WM_USER+0)
-#define WM_USER_OpenDevice  (WM_USER+0)
-#define WM_USER_ResetDevice (WM_USER+1)
-#define WM_USER_StartDevice (WM_USER+2)
-#define WM_USER_StopDevice  (WM_USER+3)
-#define WM_USER_CloseDevice (WM_USER+4)
-#define WM_USER_Enumerate   (WM_USER+5)
-#define WM_USER_Last        (WM_USER+5)
-
-static const char MessageStr[WM_USER_Last+1-WM_USER][20] = {
-    "Open Device",
-    "Reset Device",
-    "Start Device",
-    "Stop Device",
-    "Close Device",
-    "Enumerate Devices",
-};
-
-static inline void ReturnMsgResponse(ThreadRequest *req, HRESULT res)
-{
-    req->result = res;
-    SetEvent(req->FinishedEvt);
-}
-
-static HRESULT WaitForResponse(ThreadRequest *req)
-{
-    if(WaitForSingleObject(req->FinishedEvt, INFINITE) == WAIT_OBJECT_0)
-        return req->result;
-    ERR("Message response error: %lu\n", GetLastError());
-    return E_FAIL;
-}
-
-
-static void get_device_name_and_guid(IMMDevice *device, al_string *name, al_string *guid)
-{
-    IPropertyStore *ps;
-    PROPVARIANT pvname;
-    PROPVARIANT pvguid;
-    HRESULT hr;
-
-    alstr_copy_cstr(name, DEVNAME_HEAD);
-
-    hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
-    if(FAILED(hr))
-    {
-        WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
-        alstr_append_cstr(name, "Unknown Device Name");
-        if(guid!=NULL)alstr_copy_cstr(guid, "Unknown Device GUID");
-        return;
-    }
-
-    PropVariantInit(&pvname);
-
-    hr = IPropertyStore_GetValue(ps, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pvname);
-    if(FAILED(hr))
-    {
-        WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
-        alstr_append_cstr(name, "Unknown Device Name");
-    }
-    else if(pvname.vt == VT_LPWSTR)
-        alstr_append_wcstr(name, pvname.pwszVal);
-    else
-    {
-        WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvname.vt);
-        alstr_append_cstr(name, "Unknown Device Name");
-    }
-    PropVariantClear(&pvname);
-
-    if(guid!=NULL){
-        PropVariantInit(&pvguid);
-
-        hr = IPropertyStore_GetValue(ps, (const PROPERTYKEY*)&PKEY_AudioEndpoint_GUID, &pvguid);
-        if(FAILED(hr))
-        {
-            WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
-            alstr_copy_cstr(guid, "Unknown Device GUID");
-        }
-        else if(pvguid.vt == VT_LPWSTR)
-            alstr_copy_wcstr(guid, pvguid.pwszVal);
-        else
-        {
-            WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvguid.vt);
-            alstr_copy_cstr(guid, "Unknown Device GUID");
-        }
-
-        PropVariantClear(&pvguid);
-    }
-
-    IPropertyStore_Release(ps);
-}
-
-static void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
-{
-    IPropertyStore *ps;
-    PROPVARIANT pvform;
-    HRESULT hr;
-
-    hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
-    if(FAILED(hr))
-    {
-        WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
-        return;
-    }
-
-    PropVariantInit(&pvform);
-
-    hr = IPropertyStore_GetValue(ps, &PKEY_AudioEndpoint_FormFactor, &pvform);
-    if(FAILED(hr))
-        WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
-    else if(pvform.vt == VT_UI4)
-        *formfactor = pvform.ulVal;
-    else if(pvform.vt == VT_EMPTY)
-        *formfactor = UnknownFormFactor;
-    else
-        WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.vt);
-
-    PropVariantClear(&pvform);
-    IPropertyStore_Release(ps);
-}
-
-
-static void add_device(IMMDevice *device, const WCHAR *devid, vector_DevMap *list)
-{
-    int count = 0;
-    al_string tmpname;
-    DevMap entry;
-
-    AL_STRING_INIT(tmpname);
-    AL_STRING_INIT(entry.name);
-    AL_STRING_INIT(entry.endpoint_guid);
-
-    entry.devid = strdupW(devid);
-    get_device_name_and_guid(device, &tmpname, &entry.endpoint_guid);
-
-    while(1)
-    {
-        const DevMap *iter;
-
-        alstr_copy(&entry.name, tmpname);
-        if(count != 0)
-        {
-            char str[64];
-            snprintf(str, sizeof(str), " #%d", count+1);
-            alstr_append_cstr(&entry.name, str);
-        }
-
-#define MATCH_ENTRY(i) (alstr_cmp(entry.name, (i)->name) == 0)
-        VECTOR_FIND_IF(iter, const DevMap, *list, MATCH_ENTRY);
-        if(iter == VECTOR_END(*list)) break;
-#undef MATCH_ENTRY
-        count++;
-    }
-
-    TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", alstr_get_cstr(entry.name), alstr_get_cstr(entry.endpoint_guid), entry.devid);
-    VECTOR_PUSH_BACK(*list, entry);
-
-    AL_STRING_DEINIT(tmpname);
-}
-
-static WCHAR *get_device_id(IMMDevice *device)
-{
-    WCHAR *devid;
-    HRESULT hr;
-
-    hr = IMMDevice_GetId(device, &devid);
-    if(FAILED(hr))
-    {
-        ERR("Failed to get device id: %lx\n", hr);
-        return NULL;
-    }
-
-    return devid;
-}
-
-static HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, vector_DevMap *list)
-{
-    IMMDeviceCollection *coll;
-    IMMDevice *defdev = NULL;
-    WCHAR *defdevid = NULL;
-    HRESULT hr;
-    UINT count;
-    UINT i;
-
-    hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, flowdir, DEVICE_STATE_ACTIVE, &coll);
-    if(FAILED(hr))
-    {
-        ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    count = 0;
-    hr = IMMDeviceCollection_GetCount(coll, &count);
-    if(SUCCEEDED(hr) && count > 0)
-    {
-        clear_devlist(list);
-        VECTOR_RESIZE(*list, 0, count);
-
-        hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, flowdir,
-                                                         eMultimedia, &defdev);
-    }
-    if(SUCCEEDED(hr) && defdev != NULL)
-    {
-        defdevid = get_device_id(defdev);
-        if(defdevid)
-            add_device(defdev, defdevid, list);
-    }
-
-    for(i = 0;i < count;++i)
-    {
-        IMMDevice *device;
-        WCHAR *devid;
-
-        hr = IMMDeviceCollection_Item(coll, i, &device);
-        if(FAILED(hr)) continue;
-
-        devid = get_device_id(device);
-        if(devid)
-        {
-            if(wcscmp(devid, defdevid) != 0)
-                add_device(device, devid, list);
-            CoTaskMemFree(devid);
-        }
-        IMMDevice_Release(device);
-    }
-
-    if(defdev) IMMDevice_Release(defdev);
-    if(defdevid) CoTaskMemFree(defdevid);
-    IMMDeviceCollection_Release(coll);
-
-    return S_OK;
-}
-
-
-/* Proxy interface used by the message handler. */
-struct ALCwasapiProxyVtable;
-
-typedef struct ALCwasapiProxy {
-    const struct ALCwasapiProxyVtable *vtbl;
-} ALCwasapiProxy;
-
-struct ALCwasapiProxyVtable {
-    HRESULT (*const openProxy)(ALCwasapiProxy*);
-    void (*const closeProxy)(ALCwasapiProxy*);
-
-    HRESULT (*const resetProxy)(ALCwasapiProxy*);
-    HRESULT (*const startProxy)(ALCwasapiProxy*);
-    void  (*const stopProxy)(ALCwasapiProxy*);
-};
-
-#define DEFINE_ALCWASAPIPROXY_VTABLE(T)                                       \
-DECLARE_THUNK(T, ALCwasapiProxy, HRESULT, openProxy)                          \
-DECLARE_THUNK(T, ALCwasapiProxy, void, closeProxy)                            \
-DECLARE_THUNK(T, ALCwasapiProxy, HRESULT, resetProxy)                         \
-DECLARE_THUNK(T, ALCwasapiProxy, HRESULT, startProxy)                         \
-DECLARE_THUNK(T, ALCwasapiProxy, void, stopProxy)                             \
-                                                                              \
-static const struct ALCwasapiProxyVtable T##_ALCwasapiProxy_vtable = {        \
-    T##_ALCwasapiProxy_openProxy,                                             \
-    T##_ALCwasapiProxy_closeProxy,                                            \
-    T##_ALCwasapiProxy_resetProxy,                                            \
-    T##_ALCwasapiProxy_startProxy,                                            \
-    T##_ALCwasapiProxy_stopProxy,                                             \
-}
-
-static void ALCwasapiProxy_Construct(ALCwasapiProxy* UNUSED(self)) { }
-static void ALCwasapiProxy_Destruct(ALCwasapiProxy* UNUSED(self)) { }
-
-static DWORD CALLBACK ALCwasapiProxy_messageHandler(void *ptr)
-{
-    ThreadRequest *req = ptr;
-    IMMDeviceEnumerator *Enumerator;
-    ALuint deviceCount = 0;
-    ALCwasapiProxy *proxy;
-    HRESULT hr, cohr;
-    MSG msg;
-
-    TRACE("Starting message thread\n");
-
-    cohr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    if(FAILED(cohr))
-    {
-        WARN("Failed to initialize COM: 0x%08lx\n", cohr);
-        ReturnMsgResponse(req, cohr);
-        return 0;
-    }
-
-    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
-    if(FAILED(hr))
-    {
-        WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
-        CoUninitialize();
-        ReturnMsgResponse(req, hr);
-        return 0;
-    }
-    Enumerator = ptr;
-    IMMDeviceEnumerator_Release(Enumerator);
-    Enumerator = NULL;
-
-    CoUninitialize();
-
-    /* HACK: Force Windows to create a message queue for this thread before
-     * returning success, otherwise PostThreadMessage may fail if it gets
-     * called before GetMessage.
-     */
-    PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
-
-    TRACE("Message thread initialization complete\n");
-    ReturnMsgResponse(req, S_OK);
-
-    TRACE("Starting message loop\n");
-    while(GetMessage(&msg, NULL, WM_USER_First, WM_USER_Last))
-    {
-        TRACE("Got message \"%s\" (0x%04x, lparam=%p, wparam=%p)\n",
-            (msg.message >= WM_USER && msg.message <= WM_USER_Last) ?
-            MessageStr[msg.message-WM_USER] : "Unknown",
-            msg.message, (void*)msg.lParam, (void*)msg.wParam
-        );
-        switch(msg.message)
-        {
-        case WM_USER_OpenDevice:
-            req = (ThreadRequest*)msg.wParam;
-            proxy = (ALCwasapiProxy*)msg.lParam;
-
-            hr = cohr = S_OK;
-            if(++deviceCount == 1)
-                hr = cohr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-            if(SUCCEEDED(hr))
-                hr = V0(proxy,openProxy)();
-            if(FAILED(hr))
-            {
-                if(--deviceCount == 0 && SUCCEEDED(cohr))
-                    CoUninitialize();
-            }
-
-            ReturnMsgResponse(req, hr);
-            continue;
-
-        case WM_USER_ResetDevice:
-            req = (ThreadRequest*)msg.wParam;
-            proxy = (ALCwasapiProxy*)msg.lParam;
-
-            hr = V0(proxy,resetProxy)();
-            ReturnMsgResponse(req, hr);
-            continue;
-
-        case WM_USER_StartDevice:
-            req = (ThreadRequest*)msg.wParam;
-            proxy = (ALCwasapiProxy*)msg.lParam;
-
-            hr = V0(proxy,startProxy)();
-            ReturnMsgResponse(req, hr);
-            continue;
-
-        case WM_USER_StopDevice:
-            req = (ThreadRequest*)msg.wParam;
-            proxy = (ALCwasapiProxy*)msg.lParam;
-
-            V0(proxy,stopProxy)();
-            ReturnMsgResponse(req, S_OK);
-            continue;
-
-        case WM_USER_CloseDevice:
-            req = (ThreadRequest*)msg.wParam;
-            proxy = (ALCwasapiProxy*)msg.lParam;
-
-            V0(proxy,closeProxy)();
-            if(--deviceCount == 0)
-                CoUninitialize();
-
-            ReturnMsgResponse(req, S_OK);
-            continue;
-
-        case WM_USER_Enumerate:
-            req = (ThreadRequest*)msg.wParam;
-
-            hr = cohr = S_OK;
-            if(++deviceCount == 1)
-                hr = cohr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-            if(SUCCEEDED(hr))
-                hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
-            if(SUCCEEDED(hr))
-            {
-                Enumerator = ptr;
-
-                if(msg.lParam == ALL_DEVICE_PROBE)
-                    hr = probe_devices(Enumerator, eRender, &PlaybackDevices);
-                else if(msg.lParam == CAPTURE_DEVICE_PROBE)
-                    hr = probe_devices(Enumerator, eCapture, &CaptureDevices);
-
-                IMMDeviceEnumerator_Release(Enumerator);
-                Enumerator = NULL;
-            }
-
-            if(--deviceCount == 0 && SUCCEEDED(cohr))
-                CoUninitialize();
-
-            ReturnMsgResponse(req, hr);
-            continue;
-
-        default:
-            ERR("Unexpected message: %u\n", msg.message);
-            continue;
-        }
-    }
-    TRACE("Message loop finished\n");
-
-    return 0;
-}
-
-
-typedef struct ALCwasapiPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-    DERIVE_FROM_TYPE(ALCwasapiProxy);
-
-    WCHAR *devid;
-
-    IMMDevice *mmdev;
-    IAudioClient *client;
-    IAudioRenderClient *render;
-    HANDLE NotifyEvent;
-
-    HANDLE MsgEvent;
-
-    ATOMIC(UINT32) Padding;
-
-    ATOMIC(int) killNow;
-    althrd_t thread;
-} ALCwasapiPlayback;
-
-static int ALCwasapiPlayback_mixerProc(void *arg);
-
-static void ALCwasapiPlayback_Construct(ALCwasapiPlayback *self, ALCdevice *device);
-static void ALCwasapiPlayback_Destruct(ALCwasapiPlayback *self);
-static ALCenum ALCwasapiPlayback_open(ALCwasapiPlayback *self, const ALCchar *name);
-static HRESULT ALCwasapiPlayback_openProxy(ALCwasapiPlayback *self);
-static void ALCwasapiPlayback_closeProxy(ALCwasapiPlayback *self);
-static ALCboolean ALCwasapiPlayback_reset(ALCwasapiPlayback *self);
-static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self);
-static ALCboolean ALCwasapiPlayback_start(ALCwasapiPlayback *self);
-static HRESULT ALCwasapiPlayback_startProxy(ALCwasapiPlayback *self);
-static void ALCwasapiPlayback_stop(ALCwasapiPlayback *self);
-static void ALCwasapiPlayback_stopProxy(ALCwasapiPlayback *self);
-static DECLARE_FORWARD2(ALCwasapiPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
-static DECLARE_FORWARD(ALCwasapiPlayback, ALCbackend, ALCuint, availableSamples)
-static ClockLatency ALCwasapiPlayback_getClockLatency(ALCwasapiPlayback *self);
-static DECLARE_FORWARD(ALCwasapiPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCwasapiPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCwasapiPlayback)
-
-DEFINE_ALCWASAPIPROXY_VTABLE(ALCwasapiPlayback);
-DEFINE_ALCBACKEND_VTABLE(ALCwasapiPlayback);
-
-
-static void ALCwasapiPlayback_Construct(ALCwasapiPlayback *self, ALCdevice *device)
-{
-    SET_VTABLE2(ALCwasapiPlayback, ALCbackend, self);
-    SET_VTABLE2(ALCwasapiPlayback, ALCwasapiProxy, self);
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    ALCwasapiProxy_Construct(STATIC_CAST(ALCwasapiProxy, self));
-
-    self->devid = NULL;
-
-    self->mmdev = NULL;
-    self->client = NULL;
-    self->render = NULL;
-    self->NotifyEvent = NULL;
-
-    self->MsgEvent = NULL;
-
-    ATOMIC_INIT(&self->Padding, 0);
-
-    ATOMIC_INIT(&self->killNow, 0);
-}
-
-static void ALCwasapiPlayback_Destruct(ALCwasapiPlayback *self)
-{
-    if(self->MsgEvent)
-    {
-        ThreadRequest req = { self->MsgEvent, 0 };
-        if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-            (void)WaitForResponse(&req);
-
-        CloseHandle(self->MsgEvent);
-        self->MsgEvent = NULL;
-    }
-
-    if(self->NotifyEvent)
-        CloseHandle(self->NotifyEvent);
-    self->NotifyEvent = NULL;
-
-    free(self->devid);
-    self->devid = NULL;
-
-    if(self->NotifyEvent != NULL)
-        CloseHandle(self->NotifyEvent);
-    self->NotifyEvent = NULL;
-    if(self->MsgEvent != NULL)
-        CloseHandle(self->MsgEvent);
-    self->MsgEvent = NULL;
-
-    free(self->devid);
-    self->devid = NULL;
-
-    ALCwasapiProxy_Destruct(STATIC_CAST(ALCwasapiProxy, self));
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-FORCE_ALIGN static int ALCwasapiPlayback_mixerProc(void *arg)
-{
-    ALCwasapiPlayback *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    UINT32 buffer_len, written;
-    ALuint update_size, len;
-    BYTE *buffer;
-    HRESULT hr;
-
-    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    if(FAILED(hr))
-    {
-        ERR("CoInitializeEx(NULL, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
-        V0(device->Backend,lock)();
-        aluHandleDisconnect(device, "COM init failed: 0x%08lx", hr);
-        V0(device->Backend,unlock)();
-        return 1;
-    }
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    update_size = device->UpdateSize;
-    buffer_len = update_size * device->NumUpdates;
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_relaxed))
-    {
-        hr = IAudioClient_GetCurrentPadding(self->client, &written);
-        if(FAILED(hr))
-        {
-            ERR("Failed to get padding: 0x%08lx\n", hr);
-            V0(device->Backend,lock)();
-            aluHandleDisconnect(device, "Failed to retrieve buffer padding: 0x%08lx", hr);
-            V0(device->Backend,unlock)();
-            break;
-        }
-        ATOMIC_STORE(&self->Padding, written, almemory_order_relaxed);
-
-        len = buffer_len - written;
-        if(len < update_size)
-        {
-            DWORD res;
-            res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
-            if(res != WAIT_OBJECT_0)
-                ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
-            continue;
-        }
-        len -= len%update_size;
-
-        hr = IAudioRenderClient_GetBuffer(self->render, len, &buffer);
-        if(SUCCEEDED(hr))
-        {
-            ALCwasapiPlayback_lock(self);
-            aluMixData(device, buffer, len);
-            ATOMIC_STORE(&self->Padding, written + len, almemory_order_relaxed);
-            ALCwasapiPlayback_unlock(self);
-            hr = IAudioRenderClient_ReleaseBuffer(self->render, len, 0);
-        }
-        if(FAILED(hr))
-        {
-            ERR("Failed to buffer data: 0x%08lx\n", hr);
-            V0(device->Backend,lock)();
-            aluHandleDisconnect(device, "Failed to send playback samples: 0x%08lx", hr);
-            V0(device->Backend,unlock)();
-            break;
-        }
-    }
-    ATOMIC_STORE(&self->Padding, 0, almemory_order_release);
-
-    CoUninitialize();
-    return 0;
-}
-
-
-static ALCboolean MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
-{
-    memset(out, 0, sizeof(*out));
-    if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
-        *out = *(const WAVEFORMATEXTENSIBLE*)in;
-    else if(in->wFormatTag == WAVE_FORMAT_PCM)
-    {
-        out->Format = *in;
-        out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-        out->Format.cbSize = sizeof(*out) - sizeof(*in);
-        if(out->Format.nChannels == 1)
-            out->dwChannelMask = MONO;
-        else if(out->Format.nChannels == 2)
-            out->dwChannelMask = STEREO;
-        else
-            ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
-        out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-    }
-    else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
-    {
-        out->Format = *in;
-        out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-        out->Format.cbSize = sizeof(*out) - sizeof(*in);
-        if(out->Format.nChannels == 1)
-            out->dwChannelMask = MONO;
-        else if(out->Format.nChannels == 2)
-            out->dwChannelMask = STEREO;
-        else
-            ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
-        out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-    }
-    else
-    {
-        ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
-        return ALC_FALSE;
-    }
-    return ALC_TRUE;
-}
-
-static ALCenum ALCwasapiPlayback_open(ALCwasapiPlayback *self, const ALCchar *deviceName)
-{
-    HRESULT hr = S_OK;
-
-    self->NotifyEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-    self->MsgEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-    if(self->NotifyEvent == NULL || self->MsgEvent == NULL)
-    {
-        ERR("Failed to create message events: %lu\n", GetLastError());
-        hr = E_FAIL;
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        if(deviceName)
-        {
-            const DevMap *iter;
-
-            if(VECTOR_SIZE(PlaybackDevices) == 0)
-            {
-                ThreadRequest req = { self->MsgEvent, 0 };
-                if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, ALL_DEVICE_PROBE))
-                    (void)WaitForResponse(&req);
-            }
-
-            hr = E_FAIL;
-#define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 ||        \
-                       alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
-            VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME);
-#undef MATCH_NAME
-            if(iter == VECTOR_END(PlaybackDevices))
-            {
-                int len;
-                if((len=MultiByteToWideChar(CP_UTF8, 0, deviceName, -1, NULL, 0)) > 0)
-                {
-                    WCHAR *wname = calloc(sizeof(WCHAR), len);
-                    MultiByteToWideChar(CP_UTF8, 0, deviceName, -1, wname, len);
-#define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
-                    VECTOR_FIND_IF(iter, const DevMap, PlaybackDevices, MATCH_NAME);
-#undef MATCH_NAME
-                    free(wname);
-                }
-            }
-            if(iter == VECTOR_END(PlaybackDevices))
-                WARN("Failed to find device name matching \"%s\"\n", deviceName);
-            else
-            {
-                ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-                self->devid = strdupW(iter->devid);
-                alstr_copy(&device->DeviceName, iter->name);
-                hr = S_OK;
-            }
-        }
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        ThreadRequest req = { self->MsgEvent, 0 };
-
-        hr = E_FAIL;
-        if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-            hr = WaitForResponse(&req);
-        else
-            ERR("Failed to post thread message: %lu\n", GetLastError());
-    }
-
-    if(FAILED(hr))
-    {
-        if(self->NotifyEvent != NULL)
-            CloseHandle(self->NotifyEvent);
-        self->NotifyEvent = NULL;
-        if(self->MsgEvent != NULL)
-            CloseHandle(self->MsgEvent);
-        self->MsgEvent = NULL;
-
-        free(self->devid);
-        self->devid = NULL;
-
-        ERR("Device init failed: 0x%08lx\n", hr);
-        return ALC_INVALID_VALUE;
-    }
-
-    return ALC_NO_ERROR;
-}
-
-static HRESULT ALCwasapiPlayback_openProxy(ALCwasapiPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    void *ptr;
-    HRESULT hr;
-
-    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        IMMDeviceEnumerator *Enumerator = ptr;
-        if(!self->devid)
-            hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eRender, eMultimedia, &self->mmdev);
-        else
-            hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev);
-        IMMDeviceEnumerator_Release(Enumerator);
-        Enumerator = NULL;
-    }
-    if(SUCCEEDED(hr))
-        hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        self->client = ptr;
-        if(alstr_empty(device->DeviceName))
-            get_device_name_and_guid(self->mmdev, &device->DeviceName, NULL);
-    }
-
-    if(FAILED(hr))
-    {
-        if(self->mmdev)
-            IMMDevice_Release(self->mmdev);
-        self->mmdev = NULL;
-    }
-
-    return hr;
-}
-
-
-static void ALCwasapiPlayback_closeProxy(ALCwasapiPlayback *self)
-{
-    if(self->client)
-        IAudioClient_Release(self->client);
-    self->client = NULL;
-
-    if(self->mmdev)
-        IMMDevice_Release(self->mmdev);
-    self->mmdev = NULL;
-}
-
-
-static ALCboolean ALCwasapiPlayback_reset(ALCwasapiPlayback *self)
-{
-    ThreadRequest req = { self->MsgEvent, 0 };
-    HRESULT hr = E_FAIL;
-
-    if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-        hr = WaitForResponse(&req);
-
-    return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
-}
-
-static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    EndpointFormFactor formfactor = UnknownFormFactor;
-    WAVEFORMATEXTENSIBLE OutputType;
-    WAVEFORMATEX *wfx = NULL;
-    REFERENCE_TIME min_per, buf_time;
-    UINT32 buffer_len, min_len;
-    void *ptr = NULL;
-    HRESULT hr;
-
-    if(self->client)
-        IAudioClient_Release(self->client);
-    self->client = NULL;
-
-    hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
-    if(FAILED(hr))
-    {
-        ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
-        return hr;
-    }
-    self->client = ptr;
-
-    hr = IAudioClient_GetMixFormat(self->client, &wfx);
-    if(FAILED(hr))
-    {
-        ERR("Failed to get mix format: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    if(!MakeExtensible(&OutputType, wfx))
-    {
-        CoTaskMemFree(wfx);
-        return E_FAIL;
-    }
-    CoTaskMemFree(wfx);
-    wfx = NULL;
-
-    buf_time = ScaleCeil(device->UpdateSize*device->NumUpdates, REFTIME_PER_SEC,
-                         device->Frequency);
-
-    if(!(device->Flags&DEVICE_FREQUENCY_REQUEST))
-        device->Frequency = OutputType.Format.nSamplesPerSec;
-    if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
-    {
-        if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
-            device->FmtChans = DevFmtMono;
-        else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
-            device->FmtChans = DevFmtStereo;
-        else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
-            device->FmtChans = DevFmtQuad;
-        else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
-            device->FmtChans = DevFmtX51;
-        else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
-            device->FmtChans = DevFmtX51Rear;
-        else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
-            device->FmtChans = DevFmtX61;
-        else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
-            device->FmtChans = DevFmtX71;
-        else
-            ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
-    }
-
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            OutputType.Format.nChannels = 1;
-            OutputType.dwChannelMask = MONO;
-            break;
-        case DevFmtAmbi3D:
-            device->FmtChans = DevFmtStereo;
-            /*fall-through*/
-        case DevFmtStereo:
-            OutputType.Format.nChannels = 2;
-            OutputType.dwChannelMask = STEREO;
-            break;
-        case DevFmtQuad:
-            OutputType.Format.nChannels = 4;
-            OutputType.dwChannelMask = QUAD;
-            break;
-        case DevFmtX51:
-            OutputType.Format.nChannels = 6;
-            OutputType.dwChannelMask = X5DOT1;
-            break;
-        case DevFmtX51Rear:
-            OutputType.Format.nChannels = 6;
-            OutputType.dwChannelMask = X5DOT1REAR;
-            break;
-        case DevFmtX61:
-            OutputType.Format.nChannels = 7;
-            OutputType.dwChannelMask = X6DOT1;
-            break;
-        case DevFmtX71:
-            OutputType.Format.nChannels = 8;
-            OutputType.dwChannelMask = X7DOT1;
-            break;
-    }
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            device->FmtType = DevFmtUByte;
-            /* fall-through */
-        case DevFmtUByte:
-            OutputType.Format.wBitsPerSample = 8;
-            OutputType.Samples.wValidBitsPerSample = 8;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtUShort:
-            device->FmtType = DevFmtShort;
-            /* fall-through */
-        case DevFmtShort:
-            OutputType.Format.wBitsPerSample = 16;
-            OutputType.Samples.wValidBitsPerSample = 16;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtUInt:
-            device->FmtType = DevFmtInt;
-            /* fall-through */
-        case DevFmtInt:
-            OutputType.Format.wBitsPerSample = 32;
-            OutputType.Samples.wValidBitsPerSample = 32;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtFloat:
-            OutputType.Format.wBitsPerSample = 32;
-            OutputType.Samples.wValidBitsPerSample = 32;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-            break;
-    }
-    OutputType.Format.nSamplesPerSec = device->Frequency;
-
-    OutputType.Format.nBlockAlign = OutputType.Format.nChannels *
-                                    OutputType.Format.wBitsPerSample / 8;
-    OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
-                                        OutputType.Format.nBlockAlign;
-
-    hr = IAudioClient_IsFormatSupported(self->client, AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
-    if(FAILED(hr))
-    {
-        ERR("Failed to check format support: 0x%08lx\n", hr);
-        hr = IAudioClient_GetMixFormat(self->client, &wfx);
-    }
-    if(FAILED(hr))
-    {
-        ERR("Failed to find a supported format: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    if(wfx != NULL)
-    {
-        if(!MakeExtensible(&OutputType, wfx))
-        {
-            CoTaskMemFree(wfx);
-            return E_FAIL;
-        }
-        CoTaskMemFree(wfx);
-        wfx = NULL;
-
-        device->Frequency = OutputType.Format.nSamplesPerSec;
-        if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
-            device->FmtChans = DevFmtMono;
-        else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
-            device->FmtChans = DevFmtStereo;
-        else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
-            device->FmtChans = DevFmtQuad;
-        else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
-            device->FmtChans = DevFmtX51;
-        else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
-            device->FmtChans = DevFmtX51Rear;
-        else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
-            device->FmtChans = DevFmtX61;
-        else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
-            device->FmtChans = DevFmtX71;
-        else
-        {
-            ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
-            device->FmtChans = DevFmtStereo;
-            OutputType.Format.nChannels = 2;
-            OutputType.dwChannelMask = STEREO;
-        }
-
-        if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
-        {
-            if(OutputType.Format.wBitsPerSample == 8)
-                device->FmtType = DevFmtUByte;
-            else if(OutputType.Format.wBitsPerSample == 16)
-                device->FmtType = DevFmtShort;
-            else if(OutputType.Format.wBitsPerSample == 32)
-                device->FmtType = DevFmtInt;
-            else
-            {
-                device->FmtType = DevFmtShort;
-                OutputType.Format.wBitsPerSample = 16;
-            }
-        }
-        else if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
-        {
-            device->FmtType = DevFmtFloat;
-            OutputType.Format.wBitsPerSample = 32;
-        }
-        else
-        {
-            ERR("Unhandled format sub-type\n");
-            device->FmtType = DevFmtShort;
-            OutputType.Format.wBitsPerSample = 16;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-        }
-        OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
-    }
-    get_device_formfactor(self->mmdev, &formfactor);
-    device->IsHeadphones = (device->FmtChans == DevFmtStereo &&
-                            (formfactor == Headphones || formfactor == Headset)
-                           );
-
-    SetDefaultWFXChannelOrder(device);
-
-    hr = IAudioClient_Initialize(self->client, AUDCLNT_SHAREMODE_SHARED,
-                                 AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
-                                 buf_time, 0, &OutputType.Format, NULL);
-    if(FAILED(hr))
-    {
-        ERR("Failed to initialize audio client: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    hr = IAudioClient_GetDevicePeriod(self->client, &min_per, NULL);
-    if(SUCCEEDED(hr))
-    {
-        min_len = (UINT32)ScaleCeil(min_per, device->Frequency, REFTIME_PER_SEC);
-        /* Find the nearest multiple of the period size to the update size */
-        if(min_len < device->UpdateSize)
-            min_len *= (device->UpdateSize + min_len/2)/min_len;
-        hr = IAudioClient_GetBufferSize(self->client, &buffer_len);
-    }
-    if(FAILED(hr))
-    {
-        ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    device->UpdateSize = min_len;
-    device->NumUpdates = buffer_len / device->UpdateSize;
-    if(device->NumUpdates <= 1)
-    {
-        ERR("Audio client returned buffer_len < period*2; expect break up\n");
-        device->NumUpdates = 2;
-        device->UpdateSize = buffer_len / device->NumUpdates;
-    }
-
-    hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent);
-    if(FAILED(hr))
-    {
-        ERR("Failed to set event handle: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    return hr;
-}
-
-
-static ALCboolean ALCwasapiPlayback_start(ALCwasapiPlayback *self)
-{
-    ThreadRequest req = { self->MsgEvent, 0 };
-    HRESULT hr = E_FAIL;
-
-    if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-        hr = WaitForResponse(&req);
-
-    return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
-}
-
-static HRESULT ALCwasapiPlayback_startProxy(ALCwasapiPlayback *self)
-{
-    HRESULT hr;
-    void *ptr;
-
-    ResetEvent(self->NotifyEvent);
-    hr = IAudioClient_Start(self->client);
-    if(FAILED(hr))
-        ERR("Failed to start audio client: 0x%08lx\n", hr);
-
-    if(SUCCEEDED(hr))
-        hr = IAudioClient_GetService(self->client, &IID_IAudioRenderClient, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        self->render = ptr;
-        ATOMIC_STORE(&self->killNow, 0, almemory_order_release);
-        if(althrd_create(&self->thread, ALCwasapiPlayback_mixerProc, self) != althrd_success)
-        {
-            if(self->render)
-                IAudioRenderClient_Release(self->render);
-            self->render = NULL;
-            IAudioClient_Stop(self->client);
-            ERR("Failed to start thread\n");
-            hr = E_FAIL;
-        }
-    }
-
-    return hr;
-}
-
-
-static void ALCwasapiPlayback_stop(ALCwasapiPlayback *self)
-{
-    ThreadRequest req = { self->MsgEvent, 0 };
-    if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-        (void)WaitForResponse(&req);
-}
-
-static void ALCwasapiPlayback_stopProxy(ALCwasapiPlayback *self)
-{
-    int res;
-
-    if(!self->render)
-        return;
-
-    ATOMIC_STORE_SEQ(&self->killNow, 1);
-    althrd_join(self->thread, &res);
-
-    IAudioRenderClient_Release(self->render);
-    self->render = NULL;
-    IAudioClient_Stop(self->client);
-}
-
-
-static ClockLatency ALCwasapiPlayback_getClockLatency(ALCwasapiPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ClockLatency ret;
-
-    ALCwasapiPlayback_lock(self);
-    ret.ClockTime = GetDeviceClockTime(device);
-    ret.Latency = ATOMIC_LOAD(&self->Padding, almemory_order_relaxed) * DEVICE_CLOCK_RES /
-                  device->Frequency;
-    ALCwasapiPlayback_unlock(self);
-
-    return ret;
-}
-
-
-typedef struct ALCwasapiCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-    DERIVE_FROM_TYPE(ALCwasapiProxy);
-
-    WCHAR *devid;
-
-    IMMDevice *mmdev;
-    IAudioClient *client;
-    IAudioCaptureClient *capture;
-    HANDLE NotifyEvent;
-
-    HANDLE MsgEvent;
-
-    ChannelConverter *ChannelConv;
-    SampleConverter *SampleConv;
-    ll_ringbuffer_t *Ring;
-
-    ATOMIC(int) killNow;
-    althrd_t thread;
-} ALCwasapiCapture;
-
-static int ALCwasapiCapture_recordProc(void *arg);
-
-static void ALCwasapiCapture_Construct(ALCwasapiCapture *self, ALCdevice *device);
-static void ALCwasapiCapture_Destruct(ALCwasapiCapture *self);
-static ALCenum ALCwasapiCapture_open(ALCwasapiCapture *self, const ALCchar *name);
-static HRESULT ALCwasapiCapture_openProxy(ALCwasapiCapture *self);
-static void ALCwasapiCapture_closeProxy(ALCwasapiCapture *self);
-static DECLARE_FORWARD(ALCwasapiCapture, ALCbackend, ALCboolean, reset)
-static HRESULT ALCwasapiCapture_resetProxy(ALCwasapiCapture *self);
-static ALCboolean ALCwasapiCapture_start(ALCwasapiCapture *self);
-static HRESULT ALCwasapiCapture_startProxy(ALCwasapiCapture *self);
-static void ALCwasapiCapture_stop(ALCwasapiCapture *self);
-static void ALCwasapiCapture_stopProxy(ALCwasapiCapture *self);
-static ALCenum ALCwasapiCapture_captureSamples(ALCwasapiCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALuint ALCwasapiCapture_availableSamples(ALCwasapiCapture *self);
-static DECLARE_FORWARD(ALCwasapiCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCwasapiCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCwasapiCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCwasapiCapture)
-
-DEFINE_ALCWASAPIPROXY_VTABLE(ALCwasapiCapture);
-DEFINE_ALCBACKEND_VTABLE(ALCwasapiCapture);
-
-
-static void ALCwasapiCapture_Construct(ALCwasapiCapture *self, ALCdevice *device)
-{
-    SET_VTABLE2(ALCwasapiCapture, ALCbackend, self);
-    SET_VTABLE2(ALCwasapiCapture, ALCwasapiProxy, self);
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    ALCwasapiProxy_Construct(STATIC_CAST(ALCwasapiProxy, self));
-
-    self->devid = NULL;
-
-    self->mmdev = NULL;
-    self->client = NULL;
-    self->capture = NULL;
-    self->NotifyEvent = NULL;
-
-    self->MsgEvent = NULL;
-
-    self->ChannelConv = NULL;
-    self->SampleConv = NULL;
-    self->Ring = NULL;
-
-    ATOMIC_INIT(&self->killNow, 0);
-}
-
-static void ALCwasapiCapture_Destruct(ALCwasapiCapture *self)
-{
-    if(self->MsgEvent)
-    {
-        ThreadRequest req = { self->MsgEvent, 0 };
-        if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-            (void)WaitForResponse(&req);
-
-        CloseHandle(self->MsgEvent);
-        self->MsgEvent = NULL;
-    }
-
-    if(self->NotifyEvent != NULL)
-        CloseHandle(self->NotifyEvent);
-    self->NotifyEvent = NULL;
-
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = NULL;
-
-    DestroySampleConverter(&self->SampleConv);
-    DestroyChannelConverter(&self->ChannelConv);
-
-    free(self->devid);
-    self->devid = NULL;
-
-    ALCwasapiProxy_Destruct(STATIC_CAST(ALCwasapiProxy, self));
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-FORCE_ALIGN int ALCwasapiCapture_recordProc(void *arg)
-{
-    ALCwasapiCapture *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALfloat *samples = NULL;
-    size_t samplesmax = 0;
-    HRESULT hr;
-
-    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
-    if(FAILED(hr))
-    {
-        ERR("CoInitializeEx(NULL, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
-        V0(device->Backend,lock)();
-        aluHandleDisconnect(device, "COM init failed: 0x%08lx", hr);
-        V0(device->Backend,unlock)();
-        return 1;
-    }
-
-    althrd_setname(althrd_current(), RECORD_THREAD_NAME);
-
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_relaxed))
-    {
-        UINT32 avail;
-        DWORD res;
-
-        hr = IAudioCaptureClient_GetNextPacketSize(self->capture, &avail);
-        if(FAILED(hr))
-            ERR("Failed to get next packet size: 0x%08lx\n", hr);
-        else if(avail > 0)
-        {
-            UINT32 numsamples;
-            DWORD flags;
-            BYTE *rdata;
-
-            hr = IAudioCaptureClient_GetBuffer(self->capture,
-                &rdata, &numsamples, &flags, NULL, NULL
-            );
-            if(FAILED(hr))
-                ERR("Failed to get capture buffer: 0x%08lx\n", hr);
-            else
-            {
-                ll_ringbuffer_data_t data[2];
-                size_t dstframes = 0;
-
-                if(self->ChannelConv)
-                {
-                    if(samplesmax < numsamples)
-                    {
-                        size_t newmax = RoundUp(numsamples, 4096);
-                        ALfloat *tmp = al_calloc(DEF_ALIGN, newmax*2*sizeof(ALfloat));
-                        al_free(samples);
-                        samples = tmp;
-                        samplesmax = newmax;
-                    }
-                    ChannelConverterInput(self->ChannelConv, rdata, samples, numsamples);
-                    rdata = (BYTE*)samples;
-                }
-
-                ll_ringbuffer_get_write_vector(self->Ring, data);
-
-                if(self->SampleConv)
-                {
-                    const ALvoid *srcdata = rdata;
-                    ALsizei srcframes = numsamples;
-
-                    dstframes = SampleConverterInput(self->SampleConv,
-                        &srcdata, &srcframes, data[0].buf, (ALsizei)minz(data[0].len, INT_MAX)
-                    );
-                    if(srcframes > 0 && dstframes == data[0].len && data[1].len > 0)
-                    {
-                        /* If some source samples remain, all of the first dest
-                         * block was filled, and there's space in the second
-                         * dest block, do another run for the second block.
-                         */
-                        dstframes += SampleConverterInput(self->SampleConv,
-                            &srcdata, &srcframes, data[1].buf, (ALsizei)minz(data[1].len, INT_MAX)
-                        );
-                    }
-                }
-                else
-                {
-                    ALuint framesize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType,
-                                                           device->AmbiOrder);
-                    size_t len1 = minz(data[0].len, numsamples);
-                    size_t len2 = minz(data[1].len, numsamples-len1);
-
-                    memcpy(data[0].buf, rdata, len1*framesize);
-                    if(len2 > 0)
-                        memcpy(data[1].buf, rdata+len1*framesize, len2*framesize);
-                    dstframes = len1 + len2;
-                }
-
-                ll_ringbuffer_write_advance(self->Ring, dstframes);
-
-                hr = IAudioCaptureClient_ReleaseBuffer(self->capture, numsamples);
-                if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
-            }
-        }
-
-        if(FAILED(hr))
-        {
-            V0(device->Backend,lock)();
-            aluHandleDisconnect(device, "Failed to capture samples: 0x%08lx", hr);
-            V0(device->Backend,unlock)();
-            break;
-        }
-
-        res = WaitForSingleObjectEx(self->NotifyEvent, 2000, FALSE);
-        if(res != WAIT_OBJECT_0)
-            ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
-    }
-
-    al_free(samples);
-    samples = NULL;
-    samplesmax = 0;
-
-    CoUninitialize();
-    return 0;
-}
-
-
-static ALCenum ALCwasapiCapture_open(ALCwasapiCapture *self, const ALCchar *deviceName)
-{
-    HRESULT hr = S_OK;
-
-    self->NotifyEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-    self->MsgEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
-    if(self->NotifyEvent == NULL || self->MsgEvent == NULL)
-    {
-        ERR("Failed to create message events: %lu\n", GetLastError());
-        hr = E_FAIL;
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        if(deviceName)
-        {
-            const DevMap *iter;
-
-            if(VECTOR_SIZE(CaptureDevices) == 0)
-            {
-                ThreadRequest req = { self->MsgEvent, 0 };
-                if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, CAPTURE_DEVICE_PROBE))
-                    (void)WaitForResponse(&req);
-            }
-
-            hr = E_FAIL;
-#define MATCH_NAME(i) (alstr_cmp_cstr((i)->name, deviceName) == 0 ||        \
-                       alstr_cmp_cstr((i)->endpoint_guid, deviceName) == 0)
-            VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
-#undef MATCH_NAME
-            if(iter == VECTOR_END(CaptureDevices))
-            {
-                int len;
-                if((len=MultiByteToWideChar(CP_UTF8, 0, deviceName, -1, NULL, 0)) > 0)
-                {
-                    WCHAR *wname = calloc(sizeof(WCHAR), len);
-                    MultiByteToWideChar(CP_UTF8, 0, deviceName, -1, wname, len);
-#define MATCH_NAME(i) (wcscmp((i)->devid, wname) == 0)
-                    VECTOR_FIND_IF(iter, const DevMap, CaptureDevices, MATCH_NAME);
-#undef MATCH_NAME
-                    free(wname);
-                }
-            }
-            if(iter == VECTOR_END(CaptureDevices))
-                WARN("Failed to find device name matching \"%s\"\n", deviceName);
-            else
-            {
-                ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
-                self->devid = strdupW(iter->devid);
-                alstr_copy(&device->DeviceName, iter->name);
-                hr = S_OK;
-            }
-        }
-    }
-
-    if(SUCCEEDED(hr))
-    {
-        ThreadRequest req = { self->MsgEvent, 0 };
-
-        hr = E_FAIL;
-        if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-            hr = WaitForResponse(&req);
-        else
-            ERR("Failed to post thread message: %lu\n", GetLastError());
-    }
-
-    if(FAILED(hr))
-    {
-        if(self->NotifyEvent != NULL)
-            CloseHandle(self->NotifyEvent);
-        self->NotifyEvent = NULL;
-        if(self->MsgEvent != NULL)
-            CloseHandle(self->MsgEvent);
-        self->MsgEvent = NULL;
-
-        free(self->devid);
-        self->devid = NULL;
-
-        ERR("Device init failed: 0x%08lx\n", hr);
-        return ALC_INVALID_VALUE;
-    }
-    else
-    {
-        ThreadRequest req = { self->MsgEvent, 0 };
-
-        hr = E_FAIL;
-        if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-            hr = WaitForResponse(&req);
-        else
-            ERR("Failed to post thread message: %lu\n", GetLastError());
-
-        if(FAILED(hr))
-        {
-            if(hr == E_OUTOFMEMORY)
-               return ALC_OUT_OF_MEMORY;
-            return ALC_INVALID_VALUE;
-        }
-    }
-
-    return ALC_NO_ERROR;
-}
-
-static HRESULT ALCwasapiCapture_openProxy(ALCwasapiCapture *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    void *ptr;
-    HRESULT hr;
-
-    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        IMMDeviceEnumerator *Enumerator = ptr;
-        if(!self->devid)
-            hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eCapture, eMultimedia, &self->mmdev);
-        else
-            hr = IMMDeviceEnumerator_GetDevice(Enumerator, self->devid, &self->mmdev);
-        IMMDeviceEnumerator_Release(Enumerator);
-        Enumerator = NULL;
-    }
-    if(SUCCEEDED(hr))
-        hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        self->client = ptr;
-        if(alstr_empty(device->DeviceName))
-            get_device_name_and_guid(self->mmdev, &device->DeviceName, NULL);
-    }
-
-    if(FAILED(hr))
-    {
-        if(self->mmdev)
-            IMMDevice_Release(self->mmdev);
-        self->mmdev = NULL;
-    }
-
-    return hr;
-}
-
-
-static void ALCwasapiCapture_closeProxy(ALCwasapiCapture *self)
-{
-    if(self->client)
-        IAudioClient_Release(self->client);
-    self->client = NULL;
-
-    if(self->mmdev)
-        IMMDevice_Release(self->mmdev);
-    self->mmdev = NULL;
-}
-
-
-static HRESULT ALCwasapiCapture_resetProxy(ALCwasapiCapture *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    WAVEFORMATEXTENSIBLE OutputType;
-    WAVEFORMATEX *wfx = NULL;
-    enum DevFmtType srcType;
-    REFERENCE_TIME buf_time;
-    UINT32 buffer_len;
-    void *ptr = NULL;
-    HRESULT hr;
-
-    if(self->client)
-        IAudioClient_Release(self->client);
-    self->client = NULL;
-
-    hr = IMMDevice_Activate(self->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr);
-    if(FAILED(hr))
-    {
-        ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
-        return hr;
-    }
-    self->client = ptr;
-
-    buf_time = ScaleCeil(device->UpdateSize*device->NumUpdates, REFTIME_PER_SEC,
-                         device->Frequency);
-    // Make sure buffer is at least 100ms in size
-    buf_time = maxu64(buf_time, REFTIME_PER_SEC/10);
-    device->UpdateSize = (ALuint)ScaleCeil(buf_time, device->Frequency, REFTIME_PER_SEC) /
-                         device->NumUpdates;
-
-    OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-            OutputType.Format.nChannels = 1;
-            OutputType.dwChannelMask = MONO;
-            break;
-        case DevFmtStereo:
-            OutputType.Format.nChannels = 2;
-            OutputType.dwChannelMask = STEREO;
-            break;
-        case DevFmtQuad:
-            OutputType.Format.nChannels = 4;
-            OutputType.dwChannelMask = QUAD;
-            break;
-        case DevFmtX51:
-            OutputType.Format.nChannels = 6;
-            OutputType.dwChannelMask = X5DOT1;
-            break;
-        case DevFmtX51Rear:
-            OutputType.Format.nChannels = 6;
-            OutputType.dwChannelMask = X5DOT1REAR;
-            break;
-        case DevFmtX61:
-            OutputType.Format.nChannels = 7;
-            OutputType.dwChannelMask = X6DOT1;
-            break;
-        case DevFmtX71:
-            OutputType.Format.nChannels = 8;
-            OutputType.dwChannelMask = X7DOT1;
-            break;
-
-        case DevFmtAmbi3D:
-            return E_FAIL;
-    }
-    switch(device->FmtType)
-    {
-        /* NOTE: Signedness doesn't matter, the converter will handle it. */
-        case DevFmtByte:
-        case DevFmtUByte:
-            OutputType.Format.wBitsPerSample = 8;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtShort:
-        case DevFmtUShort:
-            OutputType.Format.wBitsPerSample = 16;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtInt:
-        case DevFmtUInt:
-            OutputType.Format.wBitsPerSample = 32;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
-            break;
-        case DevFmtFloat:
-            OutputType.Format.wBitsPerSample = 32;
-            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-            break;
-    }
-    OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
-    OutputType.Format.nSamplesPerSec = device->Frequency;
-
-    OutputType.Format.nBlockAlign = OutputType.Format.nChannels *
-                                    OutputType.Format.wBitsPerSample / 8;
-    OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
-                                        OutputType.Format.nBlockAlign;
-    OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format);
-
-    hr = IAudioClient_IsFormatSupported(self->client,
-        AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx
-    );
-    if(FAILED(hr))
-    {
-        ERR("Failed to check format support: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    DestroySampleConverter(&self->SampleConv);
-    DestroyChannelConverter(&self->ChannelConv);
-
-    if(wfx != NULL)
-    {
-        if(!(wfx->nChannels == OutputType.Format.nChannels ||
-             (wfx->nChannels == 1 && OutputType.Format.nChannels == 2) ||
-             (wfx->nChannels == 2 && OutputType.Format.nChannels == 1)))
-        {
-            ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
-                DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
-                device->Frequency, wfx->nChannels, (wfx->nChannels==1)?"":"s", wfx->wBitsPerSample,
-                wfx->nSamplesPerSec);
-            CoTaskMemFree(wfx);
-            return E_FAIL;
-        }
-
-        if(!MakeExtensible(&OutputType, wfx))
-        {
-            CoTaskMemFree(wfx);
-            return E_FAIL;
-        }
-        CoTaskMemFree(wfx);
-        wfx = NULL;
-    }
-
-    if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
-    {
-        if(OutputType.Format.wBitsPerSample == 8)
-            srcType = DevFmtUByte;
-        else if(OutputType.Format.wBitsPerSample == 16)
-            srcType = DevFmtShort;
-        else if(OutputType.Format.wBitsPerSample == 32)
-            srcType = DevFmtInt;
-        else
-        {
-            ERR("Unhandled integer bit depth: %d\n", OutputType.Format.wBitsPerSample);
-            return E_FAIL;
-        }
-    }
-    else if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
-    {
-        if(OutputType.Format.wBitsPerSample == 32)
-            srcType = DevFmtFloat;
-        else
-        {
-            ERR("Unhandled float bit depth: %d\n", OutputType.Format.wBitsPerSample);
-            return E_FAIL;
-        }
-    }
-    else
-    {
-        ERR("Unhandled format sub-type\n");
-        return E_FAIL;
-    }
-
-    if(device->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2)
-    {
-        self->ChannelConv = CreateChannelConverter(srcType, DevFmtStereo,
-                                                   device->FmtChans);
-        if(!self->ChannelConv)
-        {
-            ERR("Failed to create %s stereo-to-mono converter\n", DevFmtTypeString(srcType));
-            return E_FAIL;
-        }
-        TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType));
-        /* The channel converter always outputs float, so change the input type
-         * for the resampler/type-converter.
-         */
-        srcType = DevFmtFloat;
-    }
-    else if(device->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1)
-    {
-        self->ChannelConv = CreateChannelConverter(srcType, DevFmtMono,
-                                                   device->FmtChans);
-        if(!self->ChannelConv)
-        {
-            ERR("Failed to create %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
-            return E_FAIL;
-        }
-        TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
-        srcType = DevFmtFloat;
-    }
-
-    if(device->Frequency != OutputType.Format.nSamplesPerSec || device->FmtType != srcType)
-    {
-        self->SampleConv = CreateSampleConverter(
-            srcType, device->FmtType, ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder),
-            OutputType.Format.nSamplesPerSec, device->Frequency
-        );
-        if(!self->SampleConv)
-        {
-            ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
-                DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
-                device->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec);
-            return E_FAIL;
-        }
-        TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
-              DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
-              device->Frequency, DevFmtTypeString(srcType), OutputType.Format.nSamplesPerSec);
-    }
-
-    hr = IAudioClient_Initialize(self->client,
-        AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
-        buf_time, 0, &OutputType.Format, NULL
-    );
-    if(FAILED(hr))
-    {
-        ERR("Failed to initialize audio client: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    hr = IAudioClient_GetBufferSize(self->client, &buffer_len);
-    if(FAILED(hr))
-    {
-        ERR("Failed to get buffer size: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    buffer_len = maxu(device->UpdateSize*device->NumUpdates, buffer_len);
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = ll_ringbuffer_create(buffer_len,
-        FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder),
-        false
-    );
-    if(!self->Ring)
-    {
-        ERR("Failed to allocate capture ring buffer\n");
-        return E_OUTOFMEMORY;
-    }
-
-    hr = IAudioClient_SetEventHandle(self->client, self->NotifyEvent);
-    if(FAILED(hr))
-    {
-        ERR("Failed to set event handle: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    return hr;
-}
-
-
-static ALCboolean ALCwasapiCapture_start(ALCwasapiCapture *self)
-{
-    ThreadRequest req = { self->MsgEvent, 0 };
-    HRESULT hr = E_FAIL;
-
-    if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-        hr = WaitForResponse(&req);
-
-    return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
-}
-
-static HRESULT ALCwasapiCapture_startProxy(ALCwasapiCapture *self)
-{
-    HRESULT hr;
-    void *ptr;
-
-    ResetEvent(self->NotifyEvent);
-    hr = IAudioClient_Start(self->client);
-    if(FAILED(hr))
-    {
-        ERR("Failed to start audio client: 0x%08lx\n", hr);
-        return hr;
-    }
-
-    hr = IAudioClient_GetService(self->client, &IID_IAudioCaptureClient, &ptr);
-    if(SUCCEEDED(hr))
-    {
-        self->capture = ptr;
-        ATOMIC_STORE(&self->killNow, 0, almemory_order_release);
-        if(althrd_create(&self->thread, ALCwasapiCapture_recordProc, self) != althrd_success)
-        {
-            ERR("Failed to start thread\n");
-            IAudioCaptureClient_Release(self->capture);
-            self->capture = NULL;
-            hr = E_FAIL;
-        }
-    }
-
-    if(FAILED(hr))
-    {
-        IAudioClient_Stop(self->client);
-        IAudioClient_Reset(self->client);
-    }
-
-    return hr;
-}
-
-
-static void ALCwasapiCapture_stop(ALCwasapiCapture *self)
-{
-    ThreadRequest req = { self->MsgEvent, 0 };
-    if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)STATIC_CAST(ALCwasapiProxy, self)))
-        (void)WaitForResponse(&req);
-}
-
-static void ALCwasapiCapture_stopProxy(ALCwasapiCapture *self)
-{
-    int res;
-
-    if(!self->capture)
-        return;
-
-    ATOMIC_STORE_SEQ(&self->killNow, 1);
-    althrd_join(self->thread, &res);
-
-    IAudioCaptureClient_Release(self->capture);
-    self->capture = NULL;
-    IAudioClient_Stop(self->client);
-    IAudioClient_Reset(self->client);
-}
-
-
-ALuint ALCwasapiCapture_availableSamples(ALCwasapiCapture *self)
-{
-    return (ALuint)ll_ringbuffer_read_space(self->Ring);
-}
-
-ALCenum ALCwasapiCapture_captureSamples(ALCwasapiCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    if(ALCwasapiCapture_availableSamples(self) < samples)
-        return ALC_INVALID_VALUE;
-    ll_ringbuffer_read(self->Ring, buffer, samples);
-    return ALC_NO_ERROR;
-}
-
-
-static inline void AppendAllDevicesList2(const DevMap *entry)
-{ AppendAllDevicesList(alstr_get_cstr(entry->name)); }
-static inline void AppendCaptureDeviceList2(const DevMap *entry)
-{ AppendCaptureDeviceList(alstr_get_cstr(entry->name)); }
-
-typedef struct ALCwasapiBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCwasapiBackendFactory;
-#define ALCWASAPIBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwasapiBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCwasapiBackendFactory_init(ALCwasapiBackendFactory *self);
-static void ALCwasapiBackendFactory_deinit(ALCwasapiBackendFactory *self);
-static ALCboolean ALCwasapiBackendFactory_querySupport(ALCwasapiBackendFactory *self, ALCbackend_Type type);
-static void ALCwasapiBackendFactory_probe(ALCwasapiBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCwasapiBackendFactory_createBackend(ALCwasapiBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwasapiBackendFactory);
-
-
-static ALCboolean ALCwasapiBackendFactory_init(ALCwasapiBackendFactory* UNUSED(self))
-{
-    static HRESULT InitResult;
-
-    VECTOR_INIT(PlaybackDevices);
-    VECTOR_INIT(CaptureDevices);
-
-    if(!ThreadHdl)
-    {
-        ThreadRequest req;
-        InitResult = E_FAIL;
-
-        req.FinishedEvt = CreateEventW(NULL, FALSE, FALSE, NULL);
-        if(req.FinishedEvt == NULL)
-            ERR("Failed to create event: %lu\n", GetLastError());
-        else
-        {
-            ThreadHdl = CreateThread(NULL, 0, ALCwasapiProxy_messageHandler, &req, 0, &ThreadID);
-            if(ThreadHdl != NULL)
-                InitResult = WaitForResponse(&req);
-            CloseHandle(req.FinishedEvt);
-        }
-    }
-
-    return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE;
-}
-
-static void ALCwasapiBackendFactory_deinit(ALCwasapiBackendFactory* UNUSED(self))
-{
-    clear_devlist(&PlaybackDevices);
-    VECTOR_DEINIT(PlaybackDevices);
-
-    clear_devlist(&CaptureDevices);
-    VECTOR_DEINIT(CaptureDevices);
-
-    if(ThreadHdl)
-    {
-        TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID);
-        PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
-        CloseHandle(ThreadHdl);
-        ThreadHdl = NULL;
-    }
-}
-
-static ALCboolean ALCwasapiBackendFactory_querySupport(ALCwasapiBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCwasapiBackendFactory_probe(ALCwasapiBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    ThreadRequest req = { NULL, 0 };
-
-    req.FinishedEvt = CreateEventW(NULL, FALSE, FALSE, NULL);
-    if(req.FinishedEvt == NULL)
-        ERR("Failed to create event: %lu\n", GetLastError());
-    else
-    {
-        HRESULT hr = E_FAIL;
-        if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, type))
-            hr = WaitForResponse(&req);
-        if(SUCCEEDED(hr)) switch(type)
-        {
-        case ALL_DEVICE_PROBE:
-            VECTOR_FOR_EACH(const DevMap, PlaybackDevices, AppendAllDevicesList2);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            VECTOR_FOR_EACH(const DevMap, CaptureDevices, AppendCaptureDeviceList2);
-            break;
-        }
-        CloseHandle(req.FinishedEvt);
-        req.FinishedEvt = NULL;
-    }
-}
-
-static ALCbackend* ALCwasapiBackendFactory_createBackend(ALCwasapiBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCwasapiPlayback *backend;
-        NEW_OBJ(backend, ALCwasapiPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCwasapiCapture *backend;
-        NEW_OBJ(backend, ALCwasapiCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-
-ALCbackendFactory *ALCwasapiBackendFactory_getFactory(void)
-{
-    static ALCwasapiBackendFactory factory = ALCWASAPIBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 1848 - 0
Engine/lib/openal-soft/Alc/backends/wasapi.cpp

@@ -0,0 +1,1848 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2011 by authors.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "backends/wasapi.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#include <wtypes.h>
+#include <mmdeviceapi.h>
+#include <audioclient.h>
+#include <cguid.h>
+#include <devpropdef.h>
+#include <mmreg.h>
+#include <propsys.h>
+#include <propkey.h>
+#include <devpkey.h>
+#ifndef _WAVEFORMATEXTENSIBLE_
+#include <ks.h>
+#include <ksmedia.h>
+#endif
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <deque>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "albit.h"
+#include "alcmain.h"
+#include "alu.h"
+#include "compat.h"
+#include "converter.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+
+
+/* Some headers seem to define these as macros for __uuidof, which is annoying
+ * since some headers don't declare them at all. Hopefully the ifdef is enough
+ * to tell if they need to be declared.
+ */
+#ifndef KSDATAFORMAT_SUBTYPE_PCM
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+#endif
+#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+#endif
+
+DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
+DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
+DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
+
+
+namespace {
+
+using std::chrono::milliseconds;
+using std::chrono::seconds;
+
+using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>;
+
+inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept
+{ return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; }
+
+
+#define MONO SPEAKER_FRONT_CENTER
+#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
+#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
+#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
+
+constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
+{
+    b |= b>>1;
+    b |= b>>2;
+    b |= b>>4;
+    b |= b>>8;
+    b |= b>>16;
+    return b;
+}
+constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
+constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
+constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
+constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
+constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
+constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
+constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
+
+#define DEVNAME_HEAD "OpenAL Soft on "
+
+
+/* Scales the given reftime value, rounding the result. */
+inline uint RefTime2Samples(const ReferenceTime &val, uint srate)
+{
+    const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
+    return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max()));
+}
+
+
+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];
+
+public:
+    GuidPrinter(const GUID &guid)
+    {
+        std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
+            DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
+            guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
+    }
+    const char *c_str() const { return mMsg; }
+};
+
+struct PropVariant {
+    PROPVARIANT mProp;
+
+public:
+    PropVariant() { PropVariantInit(&mProp); }
+    ~PropVariant() { clear(); }
+
+    void clear() { PropVariantClear(&mProp); }
+
+    PROPVARIANT* get() noexcept { return &mProp; }
+
+    PROPVARIANT& operator*() noexcept { return mProp; }
+    const PROPVARIANT& operator*() const noexcept { return mProp; }
+
+    PROPVARIANT* operator->() noexcept { return &mProp; }
+    const PROPVARIANT* operator->() const noexcept { return &mProp; }
+};
+
+struct DevMap {
+    std::string name;
+    std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
+    std::wstring devid;
+
+    template<typename T0, typename T1, typename T2>
+    DevMap(T0&& name_, T1&& guid_, T2&& devid_)
+      : name{std::forward<T0>(name_)}
+      , endpoint_guid{std::forward<T1>(guid_)}
+      , devid{std::forward<T2>(devid_)}
+    { }
+};
+
+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();
+}
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+using NameGUIDPair = std::pair<std::string,std::string>;
+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;
+
+    ComPtr<IPropertyStore> ps;
+    HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
+    if(FAILED(hr))
+    {
+        WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
+        return std::make_pair(UnknownName, UnknownGuid);
+    }
+
+    PropVariant pvprop;
+    hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get());
+    if(FAILED(hr))
+    {
+        WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
+        name += UnknownName;
+    }
+    else if(pvprop->vt == VT_LPWSTR)
+        name += wstr_to_utf8(pvprop->pwszVal);
+    else
+    {
+        WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
+        name += UnknownName;
+    }
+
+    pvprop.clear();
+    hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get());
+    if(FAILED(hr))
+    {
+        WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
+        guid = UnknownGuid;
+    }
+    else if(pvprop->vt == VT_LPWSTR)
+        guid = wstr_to_utf8(pvprop->pwszVal);
+    else
+    {
+        WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
+        guid = UnknownGuid;
+    }
+
+    return std::make_pair(std::move(name), std::move(guid));
+}
+
+void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
+{
+    ComPtr<IPropertyStore> ps;
+    HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
+    if(FAILED(hr))
+    {
+        WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
+        return;
+    }
+
+    PropVariant pvform;
+    hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(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
+        WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt);
+}
+
+
+void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list)
+{
+    for(auto &entry : list)
+    {
+        if(entry.devid == devid)
+            return;
+    }
+
+    auto name_guid = get_device_name_and_guid(device);
+
+    int count{1};
+    std::string newname{name_guid.first};
+    while(checkName(list, newname))
+    {
+        newname = name_guid.first;
+        newname += " #";
+        newname += std::to_string(++count);
+    }
+    list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
+    const DevMap &newentry = list.back();
+
+    TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
+          newentry.endpoint_guid.c_str(), newentry.devid.c_str());
+}
+
+WCHAR *get_device_id(IMMDevice *device)
+{
+    WCHAR *devid;
+
+    const HRESULT hr{device->GetId(&devid)};
+    if(FAILED(hr))
+    {
+        ERR("Failed to get device id: %lx\n", hr);
+        return nullptr;
+    }
+
+    return devid;
+}
+
+void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
+{
+    al::vector<DevMap>{}.swap(list);
+
+    ComPtr<IMMDeviceCollection> coll;
+    HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())};
+    if(FAILED(hr))
+    {
+        ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
+        return;
+    }
+
+    UINT count{0};
+    hr = coll->GetCount(&count);
+    if(SUCCEEDED(hr) && count > 0)
+        list.reserve(count);
+
+    ComPtr<IMMDevice> device;
+    hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr());
+    if(SUCCEEDED(hr))
+    {
+        if(WCHAR *devid{get_device_id(device.get())})
+        {
+            add_device(device.get(), devid, list);
+            CoTaskMemFree(devid);
+        }
+        device = nullptr;
+    }
+
+    for(UINT i{0};i < count;++i)
+    {
+        hr = coll->Item(i, device.getPtr());
+        if(FAILED(hr)) continue;
+
+        if(WCHAR *devid{get_device_id(device.get())})
+        {
+            add_device(device.get(), devid, list);
+            CoTaskMemFree(devid);
+        }
+        device = nullptr;
+    }
+}
+
+
+bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
+{
+    *out = WAVEFORMATEXTENSIBLE{};
+    if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+    {
+        *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
+        out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
+    }
+    else if(in->wFormatTag == WAVE_FORMAT_PCM)
+    {
+        out->Format = *in;
+        out->Format.cbSize = 0;
+        out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
+        if(out->Format.nChannels == 1)
+            out->dwChannelMask = MONO;
+        else if(out->Format.nChannels == 2)
+            out->dwChannelMask = STEREO;
+        else
+            ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
+        out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+    }
+    else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
+    {
+        out->Format = *in;
+        out->Format.cbSize = 0;
+        out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
+        if(out->Format.nChannels == 1)
+            out->dwChannelMask = MONO;
+        else if(out->Format.nChannels == 2)
+            out->dwChannelMask = STEREO;
+        else
+            ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
+        out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+    }
+    else
+    {
+        ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
+        return false;
+    }
+    return true;
+}
+
+void TraceFormat(const char *msg, const WAVEFORMATEX *format)
+{
+    constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
+    if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
+    {
+        const WAVEFORMATEXTENSIBLE *fmtex{
+            CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
+        TRACE("%s:\n"
+            "    FormatTag      = 0x%04x\n"
+            "    Channels       = %d\n"
+            "    SamplesPerSec  = %lu\n"
+            "    AvgBytesPerSec = %lu\n"
+            "    BlockAlign     = %d\n"
+            "    BitsPerSample  = %d\n"
+            "    Size           = %d\n"
+            "    Samples        = %d\n"
+            "    ChannelMask    = 0x%lx\n"
+            "    SubFormat      = %s\n",
+            msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
+            fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
+            fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
+            GuidPrinter{fmtex->SubFormat}.c_str());
+    }
+    else
+        TRACE("%s:\n"
+            "    FormatTag      = 0x%04x\n"
+            "    Channels       = %d\n"
+            "    SamplesPerSec  = %lu\n"
+            "    AvgBytesPerSec = %lu\n"
+            "    BlockAlign     = %d\n"
+            "    BitsPerSample  = %d\n"
+            "    Size           = %d\n",
+            msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
+            format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
+}
+
+
+enum class MsgType {
+    OpenDevice,
+    ResetDevice,
+    StartDevice,
+    StopDevice,
+    CloseDevice,
+    EnumeratePlayback,
+    EnumerateCapture,
+    QuitThread,
+
+    Count
+};
+
+constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
+    "Open Device",
+    "Reset Device",
+    "Start Device",
+    "Stop Device",
+    "Close Device",
+    "Enumerate Playback",
+    "Enumerate Capture",
+    "Quit"
+};
+
+
+/* Proxy interface used by the message handler. */
+struct WasapiProxy {
+    virtual ~WasapiProxy() = default;
+
+    virtual HRESULT openProxy() = 0;
+    virtual void closeProxy() = 0;
+
+    virtual HRESULT resetProxy() = 0;
+    virtual HRESULT startProxy() = 0;
+    virtual void  stopProxy() = 0;
+
+    struct Msg {
+        MsgType mType;
+        WasapiProxy *mProxy;
+        std::promise<HRESULT> mPromise;
+    };
+    static std::deque<Msg> mMsgQueue;
+    static std::mutex mMsgQueueLock;
+    static std::condition_variable mMsgQueueCond;
+
+    std::future<HRESULT> pushMessage(MsgType type)
+    {
+        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)});
+        }
+        mMsgQueueCond.notify_one();
+        return future;
+    }
+
+    static std::future<HRESULT> pushMessageStatic(MsgType type)
+    {
+        std::promise<HRESULT> promise;
+        std::future<HRESULT> future{promise.get_future()};
+        {
+            std::lock_guard<std::mutex> _{mMsgQueueLock};
+            mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)});
+        }
+        mMsgQueueCond.notify_one();
+        return future;
+    }
+
+    static bool popMessage(Msg &msg)
+    {
+        std::unique_lock<std::mutex> lock{mMsgQueueLock};
+        mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
+        msg = std::move(mMsgQueue.front());
+        mMsgQueue.pop_front();
+        return msg.mType != MsgType::QuitThread;
+    }
+
+    static int messageHandler(std::promise<HRESULT> *promise);
+};
+std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue;
+std::mutex WasapiProxy::mMsgQueueLock;
+std::condition_variable WasapiProxy::mMsgQueueCond;
+
+int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
+{
+    TRACE("Starting message thread\n");
+
+    HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
+    if(FAILED(cohr))
+    {
+        WARN("Failed to initialize COM: 0x%08lx\n", cohr);
+        promise->set_value(cohr);
+        return 0;
+    }
+
+    void *ptr{};
+    HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+        IID_IMMDeviceEnumerator, &ptr)};
+    if(FAILED(hr))
+    {
+        WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
+        promise->set_value(hr);
+        CoUninitialize();
+        return 0;
+    }
+    static_cast<IMMDeviceEnumerator*>(ptr)->Release();
+    CoUninitialize();
+
+    TRACE("Message thread initialization complete\n");
+    promise->set_value(S_OK);
+    promise = nullptr;
+
+    TRACE("Starting message loop\n");
+    uint deviceCount{0};
+    Msg msg;
+    while(popMessage(msg))
+    {
+        TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
+            MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType),
+            decltype(std::declval<void*>()){msg.mProxy});
+
+        switch(msg.mType)
+        {
+        case MsgType::OpenDevice:
+            hr = cohr = S_OK;
+            if(++deviceCount == 1)
+                hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+            if(SUCCEEDED(hr))
+                hr = msg.mProxy->openProxy();
+            msg.mPromise.set_value(hr);
+
+            if(FAILED(hr))
+            {
+                if(--deviceCount == 0 && SUCCEEDED(cohr))
+                    CoUninitialize();
+            }
+            continue;
+
+        case MsgType::ResetDevice:
+            hr = msg.mProxy->resetProxy();
+            msg.mPromise.set_value(hr);
+            continue;
+
+        case MsgType::StartDevice:
+            hr = msg.mProxy->startProxy();
+            msg.mPromise.set_value(hr);
+            continue;
+
+        case MsgType::StopDevice:
+            msg.mProxy->stopProxy();
+            msg.mPromise.set_value(S_OK);
+            continue;
+
+        case MsgType::CloseDevice:
+            msg.mProxy->closeProxy();
+            msg.mPromise.set_value(S_OK);
+
+            if(--deviceCount == 0)
+                CoUninitialize();
+            continue;
+
+        case MsgType::EnumeratePlayback:
+        case MsgType::EnumerateCapture:
+            hr = cohr = S_OK;
+            if(++deviceCount == 1)
+                hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+            if(SUCCEEDED(hr))
+                hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+                    IID_IMMDeviceEnumerator, &ptr);
+            if(FAILED(hr))
+                msg.mPromise.set_value(hr);
+            else
+            {
+                ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
+
+                if(msg.mType == MsgType::EnumeratePlayback)
+                    probe_devices(enumerator.get(), eRender, PlaybackDevices);
+                else if(msg.mType == MsgType::EnumerateCapture)
+                    probe_devices(enumerator.get(), eCapture, CaptureDevices);
+                msg.mPromise.set_value(S_OK);
+            }
+
+            if(--deviceCount == 0 && SUCCEEDED(cohr))
+                CoUninitialize();
+            continue;
+
+        default:
+            ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
+            msg.mPromise.set_value(E_FAIL);
+            continue;
+        }
+    }
+    TRACE("Message loop finished\n");
+
+    return 0;
+}
+
+
+struct WasapiPlayback final : public BackendBase, WasapiProxy {
+    WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~WasapiPlayback() override;
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    HRESULT openProxy() override;
+    void closeProxy() override;
+
+    bool reset() override;
+    HRESULT resetProxy() override;
+    void start() override;
+    HRESULT startProxy() override;
+    void stop() override;
+    void stopProxy() override;
+
+    ClockLatency getClockLatency() override;
+
+    std::wstring mDevId;
+
+    HRESULT mOpenStatus{E_FAIL};
+    ComPtr<IMMDevice> mMMDev{nullptr};
+    ComPtr<IAudioClient> mClient{nullptr};
+    ComPtr<IAudioRenderClient> mRender{nullptr};
+    HANDLE mNotifyEvent{nullptr};
+
+    UINT32 mFrameStep{0u};
+    std::atomic<UINT32> mPadding{0u};
+
+    std::mutex mMutex;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(WasapiPlayback)
+};
+
+WasapiPlayback::~WasapiPlayback()
+{
+    if(SUCCEEDED(mOpenStatus))
+        pushMessage(MsgType::CloseDevice).wait();
+    mOpenStatus = E_FAIL;
+
+    if(mNotifyEvent != nullptr)
+        CloseHandle(mNotifyEvent);
+    mNotifyEvent = nullptr;
+}
+
+
+FORCE_ALIGN int WasapiPlayback::mixerProc()
+{
+    HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
+    if(FAILED(hr))
+    {
+        ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
+        mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
+        return 1;
+    }
+
+    SetRTPriority();
+    althrd_setname(MIXER_THREAD_NAME);
+
+    const uint update_size{mDevice->UpdateSize};
+    const UINT32 buffer_len{mDevice->BufferSize};
+    while(!mKillNow.load(std::memory_order_relaxed))
+    {
+        UINT32 written;
+        hr = mClient->GetCurrentPadding(&written);
+        if(FAILED(hr))
+        {
+            ERR("Failed to get padding: 0x%08lx\n", hr);
+            mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
+            break;
+        }
+        mPadding.store(written, std::memory_order_relaxed);
+
+        uint len{buffer_len - written};
+        if(len < update_size)
+        {
+            DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
+            if(res != WAIT_OBJECT_0)
+                ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
+            continue;
+        }
+
+        BYTE *buffer;
+        hr = mRender->GetBuffer(len, &buffer);
+        if(SUCCEEDED(hr))
+        {
+            {
+                std::lock_guard<std::mutex> _{mMutex};
+                mDevice->renderSamples(buffer, len, mFrameStep);
+                mPadding.store(written + len, std::memory_order_relaxed);
+            }
+            hr = mRender->ReleaseBuffer(len, 0);
+        }
+        if(FAILED(hr))
+        {
+            ERR("Failed to buffer data: 0x%08lx\n", hr);
+            mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
+            break;
+        }
+    }
+    mPadding.store(0u, std::memory_order_release);
+
+    CoUninitialize();
+    return 0;
+}
+
+
+void WasapiPlayback::open(const char *name)
+{
+    HRESULT hr{S_OK};
+
+    mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+    if(mNotifyEvent == nullptr)
+    {
+        ERR("Failed to create notify events: %lu\n", GetLastError());
+        hr = E_FAIL;
+    }
+
+    if(SUCCEEDED(hr))
+    {
+        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())
+            {
+                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;
+            }
+        }
+    }
+
+    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};
+    }
+}
+
+HRESULT WasapiPlayback::openProxy()
+{
+    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())
+            hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr());
+        else
+            hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
+    }
+    if(SUCCEEDED(hr))
+        hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+    if(SUCCEEDED(hr))
+    {
+        mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
+        if(mDevice->DeviceName.empty())
+            mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
+    }
+
+    if(FAILED(hr))
+        mMMDev = nullptr;
+
+    return hr;
+}
+
+void WasapiPlayback::closeProxy()
+{
+    mClient = nullptr;
+    mMMDev = nullptr;
+}
+
+
+bool WasapiPlayback::reset()
+{
+    HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
+    return true;
+}
+
+HRESULT WasapiPlayback::resetProxy()
+{
+    mClient = nullptr;
+
+    void *ptr;
+    HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
+    if(FAILED(hr))
+    {
+        ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+    mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
+
+    WAVEFORMATEX *wfx;
+    hr = mClient->GetMixFormat(&wfx);
+    if(FAILED(hr))
+    {
+        ERR("Failed to get mix format: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    WAVEFORMATEXTENSIBLE OutputType;
+    if(!MakeExtensible(&OutputType, wfx))
+    {
+        CoTaskMemFree(wfx);
+        return E_FAIL;
+    }
+    CoTaskMemFree(wfx);
+    wfx = nullptr;
+
+    const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
+    const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
+
+    if(!mDevice->Flags.test(FrequencyRequest))
+        mDevice->Frequency = OutputType.Format.nSamplesPerSec;
+    if(!mDevice->Flags.test(ChannelsRequest))
+    {
+        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
+            ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
+    }
+
+    OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        OutputType.Format.nChannels = 1;
+        OutputType.dwChannelMask = MONO;
+        break;
+    case DevFmtAmbi3D:
+        mDevice->FmtChans = DevFmtStereo;
+        /*fall-through*/
+    case DevFmtStereo:
+        OutputType.Format.nChannels = 2;
+        OutputType.dwChannelMask = STEREO;
+        break;
+    case DevFmtQuad:
+        OutputType.Format.nChannels = 4;
+        OutputType.dwChannelMask = QUAD;
+        break;
+    case DevFmtX51:
+        OutputType.Format.nChannels = 6;
+        OutputType.dwChannelMask = X5DOT1;
+        break;
+    case DevFmtX51Rear:
+        OutputType.Format.nChannels = 6;
+        OutputType.dwChannelMask = X5DOT1REAR;
+        break;
+    case DevFmtX61:
+        OutputType.Format.nChannels = 7;
+        OutputType.dwChannelMask = X6DOT1;
+        break;
+    case DevFmtX71:
+        OutputType.Format.nChannels = 8;
+        OutputType.dwChannelMask = X7DOT1;
+        break;
+    }
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        mDevice->FmtType = DevFmtUByte;
+        /* fall-through */
+    case DevFmtUByte:
+        OutputType.Format.wBitsPerSample = 8;
+        OutputType.Samples.wValidBitsPerSample = 8;
+        OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtUShort:
+        mDevice->FmtType = DevFmtShort;
+        /* fall-through */
+    case DevFmtShort:
+        OutputType.Format.wBitsPerSample = 16;
+        OutputType.Samples.wValidBitsPerSample = 16;
+        OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtUInt:
+        mDevice->FmtType = DevFmtInt;
+        /* fall-through */
+    case DevFmtInt:
+        OutputType.Format.wBitsPerSample = 32;
+        OutputType.Samples.wValidBitsPerSample = 32;
+        OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtFloat:
+        OutputType.Format.wBitsPerSample = 32;
+        OutputType.Samples.wValidBitsPerSample = 32;
+        OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+        break;
+    }
+    OutputType.Format.nSamplesPerSec = mDevice->Frequency;
+
+    OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
+        OutputType.Format.wBitsPerSample / 8);
+    OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
+        OutputType.Format.nBlockAlign;
+
+    TraceFormat("Requesting playback format", &OutputType.Format);
+    hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
+    if(FAILED(hr))
+    {
+        ERR("Failed to check format support: 0x%08lx\n", hr);
+        hr = mClient->GetMixFormat(&wfx);
+    }
+    if(FAILED(hr))
+    {
+        ERR("Failed to find a supported format: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    if(wfx != nullptr)
+    {
+        TraceFormat("Got playback format", wfx);
+        if(!MakeExtensible(&OutputType, wfx))
+        {
+            CoTaskMemFree(wfx);
+            return E_FAIL;
+        }
+        CoTaskMemFree(wfx);
+        wfx = nullptr;
+
+        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
+        {
+            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))
+        {
+            if(OutputType.Format.wBitsPerSample == 8)
+                mDevice->FmtType = DevFmtUByte;
+            else if(OutputType.Format.wBitsPerSample == 16)
+                mDevice->FmtType = DevFmtShort;
+            else if(OutputType.Format.wBitsPerSample == 32)
+                mDevice->FmtType = DevFmtInt;
+            else
+            {
+                mDevice->FmtType = DevFmtShort;
+                OutputType.Format.wBitsPerSample = 16;
+            }
+        }
+        else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
+        {
+            mDevice->FmtType = DevFmtFloat;
+            OutputType.Format.wBitsPerSample = 32;
+        }
+        else
+        {
+            ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
+            mDevice->FmtType = DevFmtShort;
+            if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
+                OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
+            OutputType.Format.wBitsPerSample = 16;
+            OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        }
+        OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
+    }
+    mFrameStep = OutputType.Format.nChannels;
+
+    EndpointFormFactor formfactor{UnknownFormFactor};
+    get_device_formfactor(mMMDev.get(), &formfactor);
+    mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo
+        && (formfactor == Headphones || formfactor == Headset));
+
+    setChannelOrderFromWFXMask(OutputType.dwChannelMask);
+
+    hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+        buf_time.count(), 0, &OutputType.Format, nullptr);
+    if(FAILED(hr))
+    {
+        ERR("Failed to initialize audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    UINT32 buffer_len{};
+    ReferenceTime min_per{};
+    hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
+    if(SUCCEEDED(hr))
+        hr = mClient->GetBufferSize(&buffer_len);
+    if(FAILED(hr))
+    {
+        ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    /* Find the nearest multiple of the period size to the update size */
+    if(min_per < per_time)
+        min_per *= maxi64((per_time + min_per/2) / min_per, 1);
+    mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2);
+    mDevice->BufferSize = buffer_len;
+
+    hr = mClient->SetEventHandle(mNotifyEvent);
+    if(FAILED(hr))
+    {
+        ERR("Failed to set event handle: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    return hr;
+}
+
+
+void WasapiPlayback::start()
+{
+    const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start playback: 0x%lx", hr};
+}
+
+HRESULT WasapiPlayback::startProxy()
+{
+    ResetEvent(mNotifyEvent);
+
+    HRESULT hr{mClient->Start()};
+    if(FAILED(hr))
+    {
+        ERR("Failed to start audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    void *ptr;
+    hr = mClient->GetService(IID_IAudioRenderClient, &ptr);
+    if(SUCCEEDED(hr))
+    {
+        mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)};
+        try {
+            mKillNow.store(false, std::memory_order_release);
+            mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
+        }
+        catch(...) {
+            mRender = nullptr;
+            ERR("Failed to start thread\n");
+            hr = E_FAIL;
+        }
+    }
+
+    if(FAILED(hr))
+        mClient->Stop();
+
+    return hr;
+}
+
+
+void WasapiPlayback::stop()
+{ pushMessage(MsgType::StopDevice).wait(); }
+
+void WasapiPlayback::stopProxy()
+{
+    if(!mRender || !mThread.joinable())
+        return;
+
+    mKillNow.store(true, std::memory_order_release);
+    mThread.join();
+
+    mRender = nullptr;
+    mClient->Stop();
+}
+
+
+ClockLatency WasapiPlayback::getClockLatency()
+{
+    ClockLatency ret;
+
+    std::lock_guard<std::mutex> _{mMutex};
+    ret.ClockTime = GetDeviceClockTime(mDevice);
+    ret.Latency  = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)};
+    ret.Latency /= mDevice->Frequency;
+
+    return ret;
+}
+
+
+struct WasapiCapture final : public BackendBase, WasapiProxy {
+    WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~WasapiCapture() override;
+
+    int recordProc();
+
+    void open(const char *name) override;
+    HRESULT openProxy() override;
+    void closeProxy() override;
+
+    HRESULT resetProxy() override;
+    void start() override;
+    HRESULT startProxy() override;
+    void stop() override;
+    void stopProxy() override;
+
+    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};
+    ComPtr<IAudioCaptureClient> mCapture{nullptr};
+    HANDLE mNotifyEvent{nullptr};
+
+    ChannelConverter mChannelConv{};
+    SampleConverterPtr mSampleConv;
+    RingBufferPtr mRing;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(WasapiCapture)
+};
+
+WasapiCapture::~WasapiCapture()
+{
+    if(SUCCEEDED(mOpenStatus))
+        pushMessage(MsgType::CloseDevice).wait();
+    mOpenStatus = E_FAIL;
+
+    if(mNotifyEvent != nullptr)
+        CloseHandle(mNotifyEvent);
+    mNotifyEvent = nullptr;
+}
+
+
+FORCE_ALIGN int WasapiCapture::recordProc()
+{
+    HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
+    if(FAILED(hr))
+    {
+        ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
+        mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
+        return 1;
+    }
+
+    althrd_setname(RECORD_THREAD_NAME);
+
+    al::vector<float> samples;
+    while(!mKillNow.load(std::memory_order_relaxed))
+    {
+        UINT32 avail;
+        hr = mCapture->GetNextPacketSize(&avail);
+        if(FAILED(hr))
+            ERR("Failed to get next packet size: 0x%08lx\n", hr);
+        else if(avail > 0)
+        {
+            UINT32 numsamples;
+            DWORD flags;
+            BYTE *rdata;
+
+            hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
+            if(FAILED(hr))
+                ERR("Failed to get capture buffer: 0x%08lx\n", hr);
+            else
+            {
+                if(mChannelConv.is_active())
+                {
+                    samples.resize(numsamples*2);
+                    mChannelConv.convert(rdata, samples.data(), numsamples);
+                    rdata = reinterpret_cast<BYTE*>(samples.data());
+                }
+
+                auto data = mRing->getWriteVector();
+
+                size_t dstframes;
+                if(mSampleConv)
+                {
+                    const void *srcdata{rdata};
+                    uint srcframes{numsamples};
+
+                    dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
+                        static_cast<uint>(minz(data.first.len, INT_MAX)));
+                    if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
+                    {
+                        /* If some source samples remain, all of the first dest
+                         * block was filled, and there's space in the second
+                         * dest block, do another run for the second block.
+                         */
+                        dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
+                            static_cast<uint>(minz(data.second.len, INT_MAX)));
+                    }
+                }
+                else
+                {
+                    const uint framesize{mDevice->frameSizeFromFmt()};
+                    size_t len1{minz(data.first.len, numsamples)};
+                    size_t len2{minz(data.second.len, numsamples-len1)};
+
+                    memcpy(data.first.buf, rdata, len1*framesize);
+                    if(len2 > 0)
+                        memcpy(data.second.buf, rdata+len1*framesize, len2*framesize);
+                    dstframes = len1 + len2;
+                }
+
+                mRing->writeAdvance(dstframes);
+
+                hr = mCapture->ReleaseBuffer(numsamples);
+                if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
+            }
+        }
+
+        if(FAILED(hr))
+        {
+            mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
+            break;
+        }
+
+        DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
+        if(res != WAIT_OBJECT_0)
+            ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
+    }
+
+    CoUninitialize();
+    return 0;
+}
+
+
+void WasapiCapture::open(const char *name)
+{
+    HRESULT hr{S_OK};
+
+    mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+    if(mNotifyEvent == nullptr)
+    {
+        ERR("Failed to create notify event: %lu\n", GetLastError());
+        hr = E_FAIL;
+    }
+
+    if(SUCCEEDED(hr))
+    {
+        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
+            {
+                mDevId = iter->devid;
+                mDevice->DeviceName = iter->name;
+                hr = S_OK;
+            }
+        }
+    }
+
+    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))
+    {
+        if(hr == E_OUTOFMEMORY)
+            throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
+        throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
+    }
+}
+
+HRESULT WasapiCapture::openProxy()
+{
+    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())
+            hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr());
+        else
+            hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
+    }
+    if(SUCCEEDED(hr))
+        hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+    if(SUCCEEDED(hr))
+    {
+        mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
+        if(mDevice->DeviceName.empty())
+            mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
+    }
+
+    if(FAILED(hr))
+        mMMDev = nullptr;
+
+    return hr;
+}
+
+void WasapiCapture::closeProxy()
+{
+    mClient = nullptr;
+    mMMDev = nullptr;
+}
+
+HRESULT WasapiCapture::resetProxy()
+{
+    mClient = nullptr;
+
+    void *ptr;
+    HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
+    if(FAILED(hr))
+    {
+        ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+    mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
+
+    // Make sure buffer is at least 100ms in size
+    ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
+    buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
+
+    WAVEFORMATEXTENSIBLE InputType{};
+    InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+        InputType.Format.nChannels = 1;
+        InputType.dwChannelMask = MONO;
+        break;
+    case DevFmtStereo:
+        InputType.Format.nChannels = 2;
+        InputType.dwChannelMask = STEREO;
+        break;
+    case DevFmtQuad:
+        InputType.Format.nChannels = 4;
+        InputType.dwChannelMask = QUAD;
+        break;
+    case DevFmtX51:
+        InputType.Format.nChannels = 6;
+        InputType.dwChannelMask = X5DOT1;
+        break;
+    case DevFmtX51Rear:
+        InputType.Format.nChannels = 6;
+        InputType.dwChannelMask = X5DOT1REAR;
+        break;
+    case DevFmtX61:
+        InputType.Format.nChannels = 7;
+        InputType.dwChannelMask = X6DOT1;
+        break;
+    case DevFmtX71:
+        InputType.Format.nChannels = 8;
+        InputType.dwChannelMask = X7DOT1;
+        break;
+
+    case DevFmtAmbi3D:
+        return E_FAIL;
+    }
+    switch(mDevice->FmtType)
+    {
+    /* NOTE: Signedness doesn't matter, the converter will handle it. */
+    case DevFmtByte:
+    case DevFmtUByte:
+        InputType.Format.wBitsPerSample = 8;
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtShort:
+    case DevFmtUShort:
+        InputType.Format.wBitsPerSample = 16;
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtInt:
+    case DevFmtUInt:
+        InputType.Format.wBitsPerSample = 32;
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+        break;
+    case DevFmtFloat:
+        InputType.Format.wBitsPerSample = 32;
+        InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+        break;
+    }
+    InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
+    InputType.Format.nSamplesPerSec = mDevice->Frequency;
+
+    InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
+        InputType.Format.wBitsPerSample / 8);
+    InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
+        InputType.Format.nBlockAlign;
+    InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
+
+    TraceFormat("Requesting capture format", &InputType.Format);
+    WAVEFORMATEX *wfx;
+    hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
+    if(FAILED(hr))
+    {
+        ERR("Failed to check format support: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    mSampleConv = nullptr;
+    mChannelConv = {};
+
+    if(wfx != nullptr)
+    {
+        TraceFormat("Got capture format", wfx);
+        if(!MakeExtensible(&InputType, wfx))
+        {
+            CoTaskMemFree(wfx);
+            return E_FAIL;
+        }
+        CoTaskMemFree(wfx);
+        wfx = nullptr;
+
+        auto validate_fmt = [](ALCdevice *device, uint32_t chancount, DWORD chanmask) noexcept
+            -> bool
+        {
+            switch(device->FmtChans)
+            {
+            /* If the device wants mono, we can handle any input. */
+            case DevFmtMono:
+                return true;
+            /* If the device wants stereo, we can handle mono or stereo input. */
+            case DevFmtStereo:
+                return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
+                    || (chancount == 1 && (chanmask&MonoMask) == MONO);
+            /* Otherwise, the device must match the input type. */
+            case DevFmtQuad:
+                return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
+            /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
+            case DevFmtX51:
+            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());
+            }
+            return false;
+        };
+        if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
+        {
+            ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
+                DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+                mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
+                (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
+                InputType.Format.nSamplesPerSec);
+            return E_FAIL;
+        }
+    }
+
+    DevFmtType srcType{};
+    if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
+    {
+        if(InputType.Format.wBitsPerSample == 8)
+            srcType = DevFmtUByte;
+        else if(InputType.Format.wBitsPerSample == 16)
+            srcType = DevFmtShort;
+        else if(InputType.Format.wBitsPerSample == 32)
+            srcType = DevFmtInt;
+        else
+        {
+            ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
+            return E_FAIL;
+        }
+    }
+    else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
+    {
+        if(InputType.Format.wBitsPerSample == 32)
+            srcType = DevFmtFloat;
+        else
+        {
+            ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
+            return E_FAIL;
+        }
+    }
+    else
+    {
+        ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
+        return E_FAIL;
+    }
+
+    if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
+    {
+        uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
+        /* Exclude LFE from the downmix. */
+        if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
+        {
+            constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
+            const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
+            chanmask &= ~(1u << lfeidx);
+        }
+
+        mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
+            mDevice->FmtChans};
+        TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
+        /* The channel converter always outputs float, so change the input type
+         * for the resampler/type-converter.
+         */
+        srcType = DevFmtFloat;
+    }
+    else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
+    {
+        mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
+        TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
+        srcType = DevFmtFloat;
+    }
+
+    if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
+    {
+        mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
+            InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24);
+        if(!mSampleConv)
+        {
+            ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
+                DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+                mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
+            return E_FAIL;
+        }
+        TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
+            DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+            mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
+    }
+
+    hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
+        buf_time.count(), 0, &InputType.Format, nullptr);
+    if(FAILED(hr))
+    {
+        ERR("Failed to initialize audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    UINT32 buffer_len{};
+    ReferenceTime min_per{};
+    hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
+    if(SUCCEEDED(hr))
+        hr = mClient->GetBufferSize(&buffer_len);
+    if(FAILED(hr))
+    {
+        ERR("Failed to get buffer size: 0x%08lx\n", hr);
+        return hr;
+    }
+    mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
+    mDevice->BufferSize = buffer_len;
+
+    mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
+
+    hr = mClient->SetEventHandle(mNotifyEvent);
+    if(FAILED(hr))
+    {
+        ERR("Failed to set event handle: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    return hr;
+}
+
+
+void WasapiCapture::start()
+{
+    const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+    if(FAILED(hr))
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start recording: 0x%lx", hr};
+}
+
+HRESULT WasapiCapture::startProxy()
+{
+    ResetEvent(mNotifyEvent);
+
+    HRESULT hr{mClient->Start()};
+    if(FAILED(hr))
+    {
+        ERR("Failed to start audio client: 0x%08lx\n", hr);
+        return hr;
+    }
+
+    void *ptr;
+    hr = mClient->GetService(IID_IAudioCaptureClient, &ptr);
+    if(SUCCEEDED(hr))
+    {
+        mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)};
+        try {
+            mKillNow.store(false, std::memory_order_release);
+            mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
+        }
+        catch(...) {
+            mCapture = nullptr;
+            ERR("Failed to start thread\n");
+            hr = E_FAIL;
+        }
+    }
+
+    if(FAILED(hr))
+    {
+        mClient->Stop();
+        mClient->Reset();
+    }
+
+    return hr;
+}
+
+
+void WasapiCapture::stop()
+{ pushMessage(MsgType::StopDevice).wait(); }
+
+void WasapiCapture::stopProxy()
+{
+    if(!mCapture || !mThread.joinable())
+        return;
+
+    mKillNow.store(true, std::memory_order_release);
+    mThread.join();
+
+    mCapture = nullptr;
+    mClient->Stop();
+    mClient->Reset();
+}
+
+
+void WasapiCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+uint WasapiCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+} // namespace
+
+
+bool WasapiBackendFactory::init()
+{
+    static HRESULT InitResult{E_FAIL};
+
+    if(FAILED(InitResult)) try
+    {
+        std::promise<HRESULT> promise;
+        auto future = promise.get_future();
+
+        std::thread{&WasapiProxy::messageHandler, &promise}.detach();
+        InitResult = future.get();
+    }
+    catch(...) {
+    }
+
+    return SUCCEEDED(InitResult);
+}
+
+bool WasapiBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+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);
+        break;
+
+    case BackendType::Capture:
+        WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait();
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+
+    return outnames;
+}
+
+BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new WasapiPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new WasapiCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &WasapiBackendFactory::getFactory()
+{
+    static WasapiBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/wasapi.h

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

+ 0 - 453
Engine/lib/openal-soft/Alc/backends/wave.c

@@ -1,453 +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 <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-#include <errno.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "threads.h"
-#include "compat.h"
-
-#include "backends/base.h"
-
-
-static const ALCchar waveDevice[] = "Wave File Writer";
-
-static const ALubyte SUBTYPE_PCM[] = {
-    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
-    0x00, 0x38, 0x9b, 0x71
-};
-static const ALubyte SUBTYPE_FLOAT[] = {
-    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
-    0x00, 0x38, 0x9b, 0x71
-};
-
-static const ALubyte SUBTYPE_BFORMAT_PCM[] = {
-    0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
-    0xca, 0x00, 0x00, 0x00
-};
-
-static const ALubyte SUBTYPE_BFORMAT_FLOAT[] = {
-    0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
-    0xca, 0x00, 0x00, 0x00
-};
-
-static void fwrite16le(ALushort val, FILE *f)
-{
-    ALubyte data[2] = { val&0xff, (val>>8)&0xff };
-    fwrite(data, 1, 2, f);
-}
-
-static void fwrite32le(ALuint val, FILE *f)
-{
-    ALubyte data[4] = { val&0xff, (val>>8)&0xff, (val>>16)&0xff, (val>>24)&0xff };
-    fwrite(data, 1, 4, f);
-}
-
-
-typedef struct ALCwaveBackend {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    FILE *mFile;
-    long mDataStart;
-
-    ALvoid *mBuffer;
-    ALuint mSize;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCwaveBackend;
-
-static int ALCwaveBackend_mixerProc(void *ptr);
-
-static void ALCwaveBackend_Construct(ALCwaveBackend *self, ALCdevice *device);
-static void ALCwaveBackend_Destruct(ALCwaveBackend *self);
-static ALCenum ALCwaveBackend_open(ALCwaveBackend *self, const ALCchar *name);
-static ALCboolean ALCwaveBackend_reset(ALCwaveBackend *self);
-static ALCboolean ALCwaveBackend_start(ALCwaveBackend *self);
-static void ALCwaveBackend_stop(ALCwaveBackend *self);
-static DECLARE_FORWARD2(ALCwaveBackend, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCwaveBackend, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCwaveBackend)
-
-DEFINE_ALCBACKEND_VTABLE(ALCwaveBackend);
-
-
-static void ALCwaveBackend_Construct(ALCwaveBackend *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCwaveBackend, ALCbackend, self);
-
-    self->mFile = NULL;
-    self->mDataStart = -1;
-
-    self->mBuffer = NULL;
-    self->mSize = 0;
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCwaveBackend_Destruct(ALCwaveBackend *self)
-{
-    if(self->mFile)
-        fclose(self->mFile);
-    self->mFile = NULL;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-static int ALCwaveBackend_mixerProc(void *ptr)
-{
-    ALCwaveBackend *self = (ALCwaveBackend*)ptr;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    struct timespec now, start;
-    ALint64 avail, done;
-    ALuint frameSize;
-    size_t fs;
-    const long restTime = (long)((ALuint64)device->UpdateSize * 1000000000 /
-                                 device->Frequency / 2);
-
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    done = 0;
-    if(altimespec_get(&start, AL_TIME_UTC) != AL_TIME_UTC)
-    {
-        ERR("Failed to get starting time\n");
-        return 1;
-    }
-    while(!ATOMIC_LOAD(&self->killNow, almemory_order_acquire) &&
-          ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
-    {
-        if(altimespec_get(&now, AL_TIME_UTC) != AL_TIME_UTC)
-        {
-            ERR("Failed to get current time\n");
-            return 1;
-        }
-
-        avail  = (now.tv_sec - start.tv_sec) * device->Frequency;
-        avail += (ALint64)(now.tv_nsec - start.tv_nsec) * device->Frequency / 1000000000;
-        if(avail < done)
-        {
-            /* Oops, time skipped backwards. Reset the number of samples done
-             * with one update available since we (likely) just came back from
-             * sleeping. */
-            done = avail - device->UpdateSize;
-        }
-
-        if(avail-done < device->UpdateSize)
-            al_nssleep(restTime);
-        else while(avail-done >= device->UpdateSize)
-        {
-            ALCwaveBackend_lock(self);
-            aluMixData(device, self->mBuffer, device->UpdateSize);
-            ALCwaveBackend_unlock(self);
-            done += device->UpdateSize;
-
-            if(!IS_LITTLE_ENDIAN)
-            {
-                ALuint bytesize = BytesFromDevFmt(device->FmtType);
-                ALuint i;
-
-                if(bytesize == 2)
-                {
-                    ALushort *samples = self->mBuffer;
-                    ALuint len = self->mSize / 2;
-                    for(i = 0;i < len;i++)
-                    {
-                        ALushort samp = samples[i];
-                        samples[i] = (samp>>8) | (samp<<8);
-                    }
-                }
-                else if(bytesize == 4)
-                {
-                    ALuint *samples = self->mBuffer;
-                    ALuint len = self->mSize / 4;
-                    for(i = 0;i < len;i++)
-                    {
-                        ALuint samp = samples[i];
-                        samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
-                                     ((samp<<8)&0x00ff0000) | (samp<<24);
-                    }
-                }
-            }
-
-            fs = fwrite(self->mBuffer, frameSize, device->UpdateSize, self->mFile);
-            (void)fs;
-            if(ferror(self->mFile))
-            {
-                ERR("Error writing to file\n");
-                ALCdevice_Lock(device);
-                aluHandleDisconnect(device, "Failed to write playback samples");
-                ALCdevice_Unlock(device);
-                break;
-            }
-        }
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCwaveBackend_open(ALCwaveBackend *self, const ALCchar *name)
-{
-    ALCdevice *device;
-    const char *fname;
-
-    fname = GetConfigValue(NULL, "wave", "file", "");
-    if(!fname[0]) return ALC_INVALID_VALUE;
-
-    if(!name)
-        name = waveDevice;
-    else if(strcmp(name, waveDevice) != 0)
-        return ALC_INVALID_VALUE;
-
-    self->mFile = al_fopen(fname, "wb");
-    if(!self->mFile)
-    {
-        ERR("Could not open file '%s': %s\n", fname, strerror(errno));
-        return ALC_INVALID_VALUE;
-    }
-
-    device = STATIC_CAST(ALCbackend, self)->mDevice;
-    alstr_copy_cstr(&device->DeviceName, name);
-
-    return ALC_NO_ERROR;
-}
-
-static ALCboolean ALCwaveBackend_reset(ALCwaveBackend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALuint channels=0, bits=0, chanmask=0;
-    int isbformat = 0;
-    size_t val;
-
-    fseek(self->mFile, 0, SEEK_SET);
-    clearerr(self->mFile);
-
-    if(GetConfigValueBool(NULL, "wave", "bformat", 0))
-    {
-        device->FmtChans = DevFmtAmbi3D;
-        device->AmbiOrder = 1;
-    }
-
-    switch(device->FmtType)
-    {
-        case DevFmtByte:
-            device->FmtType = DevFmtUByte;
-            break;
-        case DevFmtUShort:
-            device->FmtType = DevFmtShort;
-            break;
-        case DevFmtUInt:
-            device->FmtType = DevFmtInt;
-            break;
-        case DevFmtUByte:
-        case DevFmtShort:
-        case DevFmtInt:
-        case DevFmtFloat:
-            break;
-    }
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:   chanmask = 0x04; break;
-        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:
-            /* .amb output requires FuMa */
-            device->AmbiLayout = AmbiLayout_FuMa;
-            device->AmbiScale = AmbiNorm_FuMa;
-            isbformat = 1;
-            chanmask = 0;
-            break;
-    }
-    bits = BytesFromDevFmt(device->FmtType) * 8;
-    channels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
-    fputs("RIFF", self->mFile);
-    fwrite32le(0xFFFFFFFF, self->mFile); // 'RIFF' header len; filled in at close
-
-    fputs("WAVE", self->mFile);
-
-    fputs("fmt ", self->mFile);
-    fwrite32le(40, self->mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
-
-    // 16-bit val, format type id (extensible: 0xFFFE)
-    fwrite16le(0xFFFE, self->mFile);
-    // 16-bit val, channel count
-    fwrite16le(channels, self->mFile);
-    // 32-bit val, frequency
-    fwrite32le(device->Frequency, self->mFile);
-    // 32-bit val, bytes per second
-    fwrite32le(device->Frequency * channels * bits / 8, self->mFile);
-    // 16-bit val, frame size
-    fwrite16le(channels * bits / 8, self->mFile);
-    // 16-bit val, bits per sample
-    fwrite16le(bits, self->mFile);
-    // 16-bit val, extra byte count
-    fwrite16le(22, self->mFile);
-    // 16-bit val, valid bits per sample
-    fwrite16le(bits, self->mFile);
-    // 32-bit val, channel mask
-    fwrite32le(chanmask, self->mFile);
-    // 16 byte GUID, sub-type format
-    val = fwrite((device->FmtType == DevFmtFloat) ?
-                 (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
-                 (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, self->mFile);
-    (void)val;
-
-    fputs("data", self->mFile);
-    fwrite32le(0xFFFFFFFF, self->mFile); // 'data' header len; filled in at close
-
-    if(ferror(self->mFile))
-    {
-        ERR("Error writing header: %s\n", strerror(errno));
-        return ALC_FALSE;
-    }
-    self->mDataStart = ftell(self->mFile);
-
-    SetDefaultWFXChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCwaveBackend_start(ALCwaveBackend *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    self->mSize = device->UpdateSize * FrameSizeFromDevFmt(
-        device->FmtChans, device->FmtType, device->AmbiOrder
-    );
-    self->mBuffer = malloc(self->mSize);
-    if(!self->mBuffer)
-    {
-        ERR("Buffer malloc failed\n");
-        return ALC_FALSE;
-    }
-
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCwaveBackend_mixerProc, self) != althrd_success)
-    {
-        free(self->mBuffer);
-        self->mBuffer = NULL;
-        self->mSize = 0;
-        return ALC_FALSE;
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCwaveBackend_stop(ALCwaveBackend *self)
-{
-    ALuint dataLen;
-    long size;
-    int res;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &res);
-
-    free(self->mBuffer);
-    self->mBuffer = NULL;
-
-    size = ftell(self->mFile);
-    if(size > 0)
-    {
-        dataLen = size - self->mDataStart;
-        if(fseek(self->mFile, self->mDataStart-4, SEEK_SET) == 0)
-            fwrite32le(dataLen, self->mFile); // 'data' header len
-        if(fseek(self->mFile, 4, SEEK_SET) == 0)
-            fwrite32le(size-8, self->mFile); // 'WAVE' header len
-    }
-}
-
-
-typedef struct ALCwaveBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCwaveBackendFactory;
-#define ALCWAVEBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwaveBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *ALCwaveBackendFactory_getFactory(void);
-
-static ALCboolean ALCwaveBackendFactory_init(ALCwaveBackendFactory *self);
-static DECLARE_FORWARD(ALCwaveBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean ALCwaveBackendFactory_querySupport(ALCwaveBackendFactory *self, ALCbackend_Type type);
-static void ALCwaveBackendFactory_probe(ALCwaveBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCwaveBackendFactory_createBackend(ALCwaveBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwaveBackendFactory);
-
-
-ALCbackendFactory *ALCwaveBackendFactory_getFactory(void)
-{
-    static ALCwaveBackendFactory factory = ALCWAVEBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-
-static ALCboolean ALCwaveBackendFactory_init(ALCwaveBackendFactory* UNUSED(self))
-{
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCwaveBackendFactory_querySupport(ALCwaveBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-        return !!ConfigValueExists(NULL, "wave", "file");
-    return ALC_FALSE;
-}
-
-static void ALCwaveBackendFactory_probe(ALCwaveBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            AppendAllDevicesList(waveDevice);
-            break;
-        case CAPTURE_DEVICE_PROBE:
-            break;
-    }
-}
-
-static ALCbackend* ALCwaveBackendFactory_createBackend(ALCwaveBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCwaveBackend *backend;
-        NEW_OBJ(backend, ALCwaveBackend)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}

+ 406 - 0
Engine/lib/openal-soft/Alc/backends/wave.cpp

@@ -0,0 +1,406 @@
+/**
+ * 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 "backends/wave.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cerrno>
+#include <chrono>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <functional>
+#include <thread>
+
+#include "albit.h"
+#include "albyte.h"
+#include "alcmain.h"
+#include "alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alu.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "opthelpers.h"
+#include "strutils.h"
+#include "threads.h"
+#include "vector.h"
+
+
+namespace {
+
+using std::chrono::seconds;
+using std::chrono::milliseconds;
+using std::chrono::nanoseconds;
+
+using ubyte = unsigned char;
+using ushort = unsigned short;
+
+constexpr char waveDevice[] = "Wave File Writer";
+
+constexpr ubyte SUBTYPE_PCM[]{
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
+    0x00, 0x38, 0x9b, 0x71
+};
+constexpr ubyte SUBTYPE_FLOAT[]{
+    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
+    0x00, 0x38, 0x9b, 0x71
+};
+
+constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
+    0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
+    0xca, 0x00, 0x00, 0x00
+};
+
+constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
+    0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
+    0xca, 0x00, 0x00, 0x00
+};
+
+void fwrite16le(ushort val, FILE *f)
+{
+    ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
+    fwrite(data, 1, 2, f);
+}
+
+void fwrite32le(uint val, FILE *f)
+{
+    ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
+        static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
+    fwrite(data, 1, 4, f);
+}
+
+
+struct WaveBackend final : public BackendBase {
+    WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~WaveBackend() override;
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    FILE *mFile{nullptr};
+    long mDataStart{-1};
+
+    al::vector<al::byte> mBuffer;
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(WaveBackend)
+};
+
+WaveBackend::~WaveBackend()
+{
+    if(mFile)
+        fclose(mFile);
+    mFile = nullptr;
+}
+
+int WaveBackend::mixerProc()
+{
+    const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
+
+    althrd_setname(MIXER_THREAD_NAME);
+
+    const size_t frameStep{mDevice->channelsFromFmt()};
+    const size_t frameSize{mDevice->frameSizeFromFmt()};
+
+    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))
+    {
+        auto now = std::chrono::steady_clock::now();
+
+        /* This converts from nanoseconds to nanosamples, then to samples. */
+        int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
+            mDevice->Frequency).count()};
+        if(avail-done < mDevice->UpdateSize)
+        {
+            std::this_thread::sleep_for(restTime);
+            continue;
+        }
+        while(avail-done >= mDevice->UpdateSize)
+        {
+            mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
+            done += mDevice->UpdateSize;
+
+            if_constexpr(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));
+                    }
+                }
+                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 uint samp{samples[i]};
+                        samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
+                                     ((samp<<8)&0x00ff0000) | (samp<<24);
+                    }
+                }
+            }
+
+            const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
+            if(fs < mDevice->UpdateSize || ferror(mFile))
+            {
+                ERR("Error writing to file\n");
+                mDevice->handleDisconnect("Failed to write playback samples");
+                break;
+            }
+        }
+
+        /* For every completed second, increment the start time and reduce the
+         * samples done. This prevents the difference between the start time
+         * and current time from growing too large, while maintaining the
+         * correct number of samples to render.
+         */
+        if(done >= mDevice->Frequency)
+        {
+            seconds s{done/mDevice->Frequency};
+            done %= mDevice->Frequency;
+            start += s;
+        }
+    }
+
+    return 0;
+}
+
+void WaveBackend::open(const char *name)
+{
+    const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
+    if(!fname[0]) throw al::backend_exception{al::backend_error::NoDevice,
+        "No wave output filename"};
+
+    if(!name)
+        name = waveDevice;
+    else if(strcmp(name, waveDevice) != 0)
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+
+#ifdef _WIN32
+    {
+        std::wstring wname = utf8_to_wstr(fname);
+        mFile = _wfopen(wname.c_str(), L"wb");
+    }
+#else
+    mFile = fopen(fname, "wb");
+#endif
+    if(!mFile)
+        throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
+            fname, strerror(errno)};
+
+    mDevice->DeviceName = name;
+}
+
+bool WaveBackend::reset()
+{
+    uint channels{0}, bytes{0}, chanmask{0};
+    bool isbformat{false};
+    size_t val;
+
+    fseek(mFile, 0, SEEK_SET);
+    clearerr(mFile);
+
+    if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
+    {
+        mDevice->FmtChans = DevFmtAmbi3D;
+        mDevice->mAmbiOrder = 1;
+    }
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtByte:
+        mDevice->FmtType = DevFmtUByte;
+        break;
+    case DevFmtUShort:
+        mDevice->FmtType = DevFmtShort;
+        break;
+    case DevFmtUInt:
+        mDevice->FmtType = DevFmtInt;
+        break;
+    case DevFmtUByte:
+    case DevFmtShort:
+    case DevFmtInt:
+    case DevFmtFloat:
+        break;
+    }
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:   chanmask = 0x04; break;
+    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:
+        /* .amb output requires FuMa */
+        mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
+        mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
+        mDevice->mAmbiScale = DevAmbiScaling::FuMa;
+        isbformat = true;
+        chanmask = 0;
+        break;
+    }
+    bytes = mDevice->bytesFromFmt();
+    channels = mDevice->channelsFromFmt();
+
+    rewind(mFile);
+
+    fputs("RIFF", mFile);
+    fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
+
+    fputs("WAVE", mFile);
+
+    fputs("fmt ", mFile);
+    fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
+
+    // 16-bit val, format type id (extensible: 0xFFFE)
+    fwrite16le(0xFFFE, mFile);
+    // 16-bit val, channel count
+    fwrite16le(static_cast<ushort>(channels), mFile);
+    // 32-bit val, frequency
+    fwrite32le(mDevice->Frequency, mFile);
+    // 32-bit val, bytes per second
+    fwrite32le(mDevice->Frequency * channels * bytes, mFile);
+    // 16-bit val, frame size
+    fwrite16le(static_cast<ushort>(channels * bytes), mFile);
+    // 16-bit val, bits per sample
+    fwrite16le(static_cast<ushort>(bytes * 8), mFile);
+    // 16-bit val, extra byte count
+    fwrite16le(22, mFile);
+    // 16-bit val, valid bits per sample
+    fwrite16le(static_cast<ushort>(bytes * 8), mFile);
+    // 32-bit val, channel mask
+    fwrite32le(chanmask, mFile);
+    // 16 byte GUID, sub-type format
+    val = fwrite((mDevice->FmtType == DevFmtFloat) ?
+        (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
+        (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
+    (void)val;
+
+    fputs("data", mFile);
+    fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
+
+    if(ferror(mFile))
+    {
+        ERR("Error writing header: %s\n", strerror(errno));
+        return false;
+    }
+    mDataStart = ftell(mFile);
+
+    setDefaultWFXChannelOrder();
+
+    const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
+    mBuffer.resize(bufsize);
+
+    return true;
+}
+
+void WaveBackend::start()
+{
+    if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
+        WARN("Failed to seek on output file\n");
+    try {
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void WaveBackend::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    if(mDataStart > 0)
+    {
+        long size{ftell(mFile)};
+        if(size > 0)
+        {
+            long dataLen{size - mDataStart};
+            if(fseek(mFile, 4, SEEK_SET) == 0)
+                fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
+            if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
+                fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
+        }
+    }
+}
+
+} // namespace
+
+
+bool WaveBackendFactory::init()
+{ return true; }
+
+bool WaveBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback; }
+
+std::string WaveBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    switch(type)
+    {
+    case BackendType::Playback:
+        /* Includes null char. */
+        outnames.append(waveDevice, sizeof(waveDevice));
+        break;
+    case BackendType::Capture:
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new WaveBackend{device}};
+    return nullptr;
+}
+
+BackendFactory &WaveBackendFactory::getFactory()
+{
+    static WaveBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/wave.h

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

+ 0 - 792
Engine/lib/openal-soft/Alc/backends/winmm.c

@@ -1,792 +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 <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-
-#include <windows.h>
-#include <mmsystem.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "ringbuffer.h"
-#include "threads.h"
-
-#include "backends/base.h"
-
-#ifndef WAVE_FORMAT_IEEE_FLOAT
-#define WAVE_FORMAT_IEEE_FLOAT  0x0003
-#endif
-
-#define DEVNAME_HEAD "OpenAL Soft on "
-
-
-static vector_al_string PlaybackDevices;
-static vector_al_string CaptureDevices;
-
-static void clear_devlist(vector_al_string *list)
-{
-    VECTOR_FOR_EACH(al_string, *list, alstr_reset);
-    VECTOR_RESIZE(*list, 0, 0);
-}
-
-
-static void ProbePlaybackDevices(void)
-{
-    ALuint numdevs;
-    ALuint i;
-
-    clear_devlist(&PlaybackDevices);
-
-    numdevs = waveOutGetNumDevs();
-    VECTOR_RESIZE(PlaybackDevices, 0, numdevs);
-    for(i = 0;i < numdevs;i++)
-    {
-        WAVEOUTCAPSW WaveCaps;
-        const al_string *iter;
-        al_string dname;
-
-        AL_STRING_INIT(dname);
-        if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
-        {
-            ALuint count = 0;
-            while(1)
-            {
-                alstr_copy_cstr(&dname, DEVNAME_HEAD);
-                alstr_append_wcstr(&dname, WaveCaps.szPname);
-                if(count != 0)
-                {
-                    char str[64];
-                    snprintf(str, sizeof(str), " #%d", count+1);
-                    alstr_append_cstr(&dname, str);
-                }
-                count++;
-
-#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
-                VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_ENTRY);
-                if(iter == VECTOR_END(PlaybackDevices)) break;
-#undef MATCH_ENTRY
-            }
-
-            TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
-        }
-        VECTOR_PUSH_BACK(PlaybackDevices, dname);
-    }
-}
-
-static void ProbeCaptureDevices(void)
-{
-    ALuint numdevs;
-    ALuint i;
-
-    clear_devlist(&CaptureDevices);
-
-    numdevs = waveInGetNumDevs();
-    VECTOR_RESIZE(CaptureDevices, 0, numdevs);
-    for(i = 0;i < numdevs;i++)
-    {
-        WAVEINCAPSW WaveCaps;
-        const al_string *iter;
-        al_string dname;
-
-        AL_STRING_INIT(dname);
-        if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
-        {
-            ALuint count = 0;
-            while(1)
-            {
-                alstr_copy_cstr(&dname, DEVNAME_HEAD);
-                alstr_append_wcstr(&dname, WaveCaps.szPname);
-                if(count != 0)
-                {
-                    char str[64];
-                    snprintf(str, sizeof(str), " #%d", count+1);
-                    alstr_append_cstr(&dname, str);
-                }
-                count++;
-
-#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
-                VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_ENTRY);
-                if(iter == VECTOR_END(CaptureDevices)) break;
-#undef MATCH_ENTRY
-            }
-
-            TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
-        }
-        VECTOR_PUSH_BACK(CaptureDevices, dname);
-    }
-}
-
-
-typedef struct ALCwinmmPlayback {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    RefCount WaveBuffersCommitted;
-    WAVEHDR WaveBuffer[4];
-
-    HWAVEOUT OutHdl;
-
-    WAVEFORMATEX Format;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCwinmmPlayback;
-
-static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device);
-static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self);
-
-static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
-static int ALCwinmmPlayback_mixerProc(void *arg);
-
-static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *name);
-static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self);
-static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self);
-static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self);
-static DECLARE_FORWARD2(ALCwinmmPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
-static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCwinmmPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(ALCwinmmPlayback);
-
-
-static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCwinmmPlayback, ALCbackend, self);
-
-    InitRef(&self->WaveBuffersCommitted, 0);
-    self->OutHdl = NULL;
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self)
-{
-    if(self->OutHdl)
-        waveOutClose(self->OutHdl);
-    self->OutHdl = 0;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-/* ALCwinmmPlayback_waveOutProc
- *
- * Posts a message to 'ALCwinmmPlayback_mixerProc' everytime a WaveOut Buffer
- * is completed and returns to the application (for more data)
- */
-static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
-{
-    ALCwinmmPlayback *self = (ALCwinmmPlayback*)instance;
-
-    if(msg != WOM_DONE)
-        return;
-
-    DecrementRef(&self->WaveBuffersCommitted);
-    PostThreadMessage(self->thread, msg, 0, param1);
-}
-
-FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg)
-{
-    ALCwinmmPlayback *self = arg;
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    WAVEHDR *WaveHdr;
-    MSG msg;
-
-    SetRTPriority();
-    althrd_setname(althrd_current(), MIXER_THREAD_NAME);
-
-    while(GetMessage(&msg, NULL, 0, 0))
-    {
-        if(msg.message != WOM_DONE)
-            continue;
-
-        if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
-        {
-            if(ReadRef(&self->WaveBuffersCommitted) == 0)
-                break;
-            continue;
-        }
-
-        WaveHdr = ((WAVEHDR*)msg.lParam);
-        ALCwinmmPlayback_lock(self);
-        aluMixData(device, WaveHdr->lpData, WaveHdr->dwBufferLength /
-                                            self->Format.nBlockAlign);
-        ALCwinmmPlayback_unlock(self);
-
-        // Send buffer back to play more data
-        waveOutWrite(self->OutHdl, WaveHdr, sizeof(WAVEHDR));
-        IncrementRef(&self->WaveBuffersCommitted);
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *deviceName)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const al_string *iter;
-    UINT DeviceID;
-    MMRESULT res;
-
-    if(VECTOR_SIZE(PlaybackDevices) == 0)
-        ProbePlaybackDevices();
-
-    // Find the Device ID matching the deviceName if valid
-#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && \
-                             (!deviceName || alstr_cmp_cstr(*(iter), deviceName) == 0))
-    VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_DEVNAME);
-    if(iter == VECTOR_END(PlaybackDevices))
-        return ALC_INVALID_VALUE;
-#undef MATCH_DEVNAME
-
-    DeviceID = (UINT)(iter - VECTOR_BEGIN(PlaybackDevices));
-
-retry_open:
-    memset(&self->Format, 0, sizeof(WAVEFORMATEX));
-    if(device->FmtType == DevFmtFloat)
-    {
-        self->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
-        self->Format.wBitsPerSample = 32;
-    }
-    else
-    {
-        self->Format.wFormatTag = WAVE_FORMAT_PCM;
-        if(device->FmtType == DevFmtUByte || device->FmtType == DevFmtByte)
-            self->Format.wBitsPerSample = 8;
-        else
-            self->Format.wBitsPerSample = 16;
-    }
-    self->Format.nChannels = ((device->FmtChans == DevFmtMono) ? 1 : 2);
-    self->Format.nBlockAlign = self->Format.wBitsPerSample *
-                               self->Format.nChannels / 8;
-    self->Format.nSamplesPerSec = device->Frequency;
-    self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
-                                   self->Format.nBlockAlign;
-    self->Format.cbSize = 0;
-
-    if((res=waveOutOpen(&self->OutHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmPlayback_waveOutProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
-    {
-        if(device->FmtType == DevFmtFloat)
-        {
-            device->FmtType = DevFmtShort;
-            goto retry_open;
-        }
-        ERR("waveOutOpen failed: %u\n", res);
-        goto failure;
-    }
-
-    alstr_copy(&device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID));
-    return ALC_NO_ERROR;
-
-failure:
-    if(self->OutHdl)
-        waveOutClose(self->OutHdl);
-    self->OutHdl = NULL;
-
-    return ALC_INVALID_VALUE;
-}
-
-static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-
-    device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize *
-                                  self->Format.nSamplesPerSec /
-                                  device->Frequency);
-    device->UpdateSize = (device->UpdateSize*device->NumUpdates + 3) / 4;
-    device->NumUpdates = 4;
-    device->Frequency = self->Format.nSamplesPerSec;
-
-    if(self->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
-    {
-        if(self->Format.wBitsPerSample == 32)
-            device->FmtType = DevFmtFloat;
-        else
-        {
-            ERR("Unhandled IEEE float sample depth: %d\n", self->Format.wBitsPerSample);
-            return ALC_FALSE;
-        }
-    }
-    else if(self->Format.wFormatTag == WAVE_FORMAT_PCM)
-    {
-        if(self->Format.wBitsPerSample == 16)
-            device->FmtType = DevFmtShort;
-        else if(self->Format.wBitsPerSample == 8)
-            device->FmtType = DevFmtUByte;
-        else
-        {
-            ERR("Unhandled PCM sample depth: %d\n", self->Format.wBitsPerSample);
-            return ALC_FALSE;
-        }
-    }
-    else
-    {
-        ERR("Unhandled format tag: 0x%04x\n", self->Format.wFormatTag);
-        return ALC_FALSE;
-    }
-
-    if(self->Format.nChannels == 2)
-        device->FmtChans = DevFmtStereo;
-    else if(self->Format.nChannels == 1)
-        device->FmtChans = DevFmtMono;
-    else
-    {
-        ERR("Unhandled channel count: %d\n", self->Format.nChannels);
-        return ALC_FALSE;
-    }
-    SetDefaultWFXChannelOrder(device);
-
-    return ALC_TRUE;
-}
-
-static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    ALbyte *BufferData;
-    ALint BufferSize;
-    ALuint i;
-
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCwinmmPlayback_mixerProc, self) != althrd_success)
-        return ALC_FALSE;
-
-    InitRef(&self->WaveBuffersCommitted, 0);
-
-    // Create 4 Buffers
-    BufferSize  = device->UpdateSize*device->NumUpdates / 4;
-    BufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
-
-    BufferData = calloc(4, BufferSize);
-    for(i = 0;i < 4;i++)
-    {
-        memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
-        self->WaveBuffer[i].dwBufferLength = BufferSize;
-        self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
-                                      (self->WaveBuffer[i-1].lpData +
-                                       self->WaveBuffer[i-1].dwBufferLength));
-        waveOutPrepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        waveOutWrite(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        IncrementRef(&self->WaveBuffersCommitted);
-    }
-
-    return ALC_TRUE;
-}
-
-static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self)
-{
-    void *buffer = NULL;
-    int i;
-
-    if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-        return;
-    althrd_join(self->thread, &i);
-
-    // Release the wave buffers
-    for(i = 0;i < 4;i++)
-    {
-        waveOutUnprepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        if(i == 0) buffer = self->WaveBuffer[i].lpData;
-        self->WaveBuffer[i].lpData = NULL;
-    }
-    free(buffer);
-}
-
-
-
-typedef struct ALCwinmmCapture {
-    DERIVE_FROM_TYPE(ALCbackend);
-
-    RefCount WaveBuffersCommitted;
-    WAVEHDR WaveBuffer[4];
-
-    HWAVEIN InHdl;
-
-    ll_ringbuffer_t *Ring;
-
-    WAVEFORMATEX Format;
-
-    ATOMIC(ALenum) killNow;
-    althrd_t thread;
-} ALCwinmmCapture;
-
-static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device);
-static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self);
-
-static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
-static int ALCwinmmCapture_captureProc(void *arg);
-
-static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self);
-static void ALCwinmmCapture_stop(ALCwinmmCapture *self);
-static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples);
-static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self);
-static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(ALCwinmmCapture)
-
-DEFINE_ALCBACKEND_VTABLE(ALCwinmmCapture);
-
-
-static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device)
-{
-    ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
-    SET_VTABLE2(ALCwinmmCapture, ALCbackend, self);
-
-    InitRef(&self->WaveBuffersCommitted, 0);
-    self->InHdl = NULL;
-
-    ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self)
-{
-    void *buffer = NULL;
-    int i;
-
-    /* Tell the processing thread to quit and wait for it to do so. */
-    if(!ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
-    {
-        PostThreadMessage(self->thread, WM_QUIT, 0, 0);
-
-        althrd_join(self->thread, &i);
-
-        /* Make sure capture is stopped and all pending buffers are flushed. */
-        waveInReset(self->InHdl);
-
-        // Release the wave buffers
-        for(i = 0;i < 4;i++)
-        {
-            waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-            if(i == 0) buffer = self->WaveBuffer[i].lpData;
-            self->WaveBuffer[i].lpData = NULL;
-        }
-        free(buffer);
-    }
-
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = NULL;
-
-    // Close the Wave device
-    if(self->InHdl)
-        waveInClose(self->InHdl);
-    self->InHdl = 0;
-
-    ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-/* ALCwinmmCapture_waveInProc
- *
- * Posts a message to 'ALCwinmmCapture_captureProc' everytime a WaveIn Buffer
- * is completed and returns to the application (with more data).
- */
-static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
-{
-    ALCwinmmCapture *self = (ALCwinmmCapture*)instance;
-
-    if(msg != WIM_DATA)
-        return;
-
-    DecrementRef(&self->WaveBuffersCommitted);
-    PostThreadMessage(self->thread, msg, 0, param1);
-}
-
-static int ALCwinmmCapture_captureProc(void *arg)
-{
-    ALCwinmmCapture *self = arg;
-    WAVEHDR *WaveHdr;
-    MSG msg;
-
-    althrd_setname(althrd_current(), RECORD_THREAD_NAME);
-
-    while(GetMessage(&msg, NULL, 0, 0))
-    {
-        if(msg.message != WIM_DATA)
-            continue;
-        /* Don't wait for other buffers to finish before quitting. We're
-         * closing so we don't need them. */
-        if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
-            break;
-
-        WaveHdr = ((WAVEHDR*)msg.lParam);
-        ll_ringbuffer_write(self->Ring, WaveHdr->lpData,
-            WaveHdr->dwBytesRecorded / self->Format.nBlockAlign
-        );
-
-        // Send buffer back to capture more data
-        waveInAddBuffer(self->InHdl, WaveHdr, sizeof(WAVEHDR));
-        IncrementRef(&self->WaveBuffersCommitted);
-    }
-
-    return 0;
-}
-
-
-static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name)
-{
-    ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
-    const al_string *iter;
-    ALbyte *BufferData = NULL;
-    DWORD CapturedDataSize;
-    ALint BufferSize;
-    UINT DeviceID;
-    MMRESULT res;
-    ALuint i;
-
-    if(VECTOR_SIZE(CaptureDevices) == 0)
-        ProbeCaptureDevices();
-
-    // Find the Device ID matching the deviceName if valid
-#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && (!name || alstr_cmp_cstr(*iter, name) == 0))
-    VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_DEVNAME);
-    if(iter == VECTOR_END(CaptureDevices))
-        return ALC_INVALID_VALUE;
-#undef MATCH_DEVNAME
-
-    DeviceID = (UINT)(iter - VECTOR_BEGIN(CaptureDevices));
-
-    switch(device->FmtChans)
-    {
-        case DevFmtMono:
-        case DevFmtStereo:
-            break;
-
-        case DevFmtQuad:
-        case DevFmtX51:
-        case DevFmtX51Rear:
-        case DevFmtX61:
-        case DevFmtX71:
-        case DevFmtAmbi3D:
-            return ALC_INVALID_ENUM;
-    }
-
-    switch(device->FmtType)
-    {
-        case DevFmtUByte:
-        case DevFmtShort:
-        case DevFmtInt:
-        case DevFmtFloat:
-            break;
-
-        case DevFmtByte:
-        case DevFmtUShort:
-        case DevFmtUInt:
-            return ALC_INVALID_ENUM;
-    }
-
-    memset(&self->Format, 0, sizeof(WAVEFORMATEX));
-    self->Format.wFormatTag = ((device->FmtType == DevFmtFloat) ?
-                               WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
-    self->Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-    self->Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
-    self->Format.nBlockAlign = self->Format.wBitsPerSample *
-                               self->Format.nChannels / 8;
-    self->Format.nSamplesPerSec = device->Frequency;
-    self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
-                                   self->Format.nBlockAlign;
-    self->Format.cbSize = 0;
-
-    if((res=waveInOpen(&self->InHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmCapture_waveInProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
-    {
-        ERR("waveInOpen failed: %u\n", res);
-        goto failure;
-    }
-
-    // Allocate circular memory buffer for the captured audio
-    CapturedDataSize = device->UpdateSize*device->NumUpdates;
-
-    // Make sure circular buffer is at least 100ms in size
-    if(CapturedDataSize < (self->Format.nSamplesPerSec / 10))
-        CapturedDataSize = self->Format.nSamplesPerSec / 10;
-
-    self->Ring = ll_ringbuffer_create(CapturedDataSize, self->Format.nBlockAlign, false);
-    if(!self->Ring) goto failure;
-
-    InitRef(&self->WaveBuffersCommitted, 0);
-
-    // Create 4 Buffers of 50ms each
-    BufferSize = self->Format.nAvgBytesPerSec / 20;
-    BufferSize -= (BufferSize % self->Format.nBlockAlign);
-
-    BufferData = calloc(4, BufferSize);
-    if(!BufferData) goto failure;
-
-    for(i = 0;i < 4;i++)
-    {
-        memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
-        self->WaveBuffer[i].dwBufferLength = BufferSize;
-        self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
-                                      (self->WaveBuffer[i-1].lpData +
-                                       self->WaveBuffer[i-1].dwBufferLength));
-        self->WaveBuffer[i].dwFlags = 0;
-        self->WaveBuffer[i].dwLoops = 0;
-        waveInPrepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        waveInAddBuffer(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        IncrementRef(&self->WaveBuffersCommitted);
-    }
-
-    ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
-    if(althrd_create(&self->thread, ALCwinmmCapture_captureProc, self) != althrd_success)
-        goto failure;
-
-    alstr_copy(&device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID));
-    return ALC_NO_ERROR;
-
-failure:
-    if(BufferData)
-    {
-        for(i = 0;i < 4;i++)
-            waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
-        free(BufferData);
-    }
-
-    ll_ringbuffer_free(self->Ring);
-    self->Ring = NULL;
-
-    if(self->InHdl)
-        waveInClose(self->InHdl);
-    self->InHdl = NULL;
-
-    return ALC_INVALID_VALUE;
-}
-
-static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self)
-{
-    waveInStart(self->InHdl);
-    return ALC_TRUE;
-}
-
-static void ALCwinmmCapture_stop(ALCwinmmCapture *self)
-{
-    waveInStop(self->InHdl);
-}
-
-static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples)
-{
-    ll_ringbuffer_read(self->Ring, buffer, samples);
-    return ALC_NO_ERROR;
-}
-
-static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self)
-{
-    return (ALCuint)ll_ringbuffer_read_space(self->Ring);
-}
-
-
-static inline void AppendAllDevicesList2(const al_string *name)
-{
-    if(!alstr_empty(*name))
-        AppendAllDevicesList(alstr_get_cstr(*name));
-}
-static inline void AppendCaptureDeviceList2(const al_string *name)
-{
-    if(!alstr_empty(*name))
-        AppendCaptureDeviceList(alstr_get_cstr(*name));
-}
-
-typedef struct ALCwinmmBackendFactory {
-    DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCwinmmBackendFactory;
-#define ALCWINMMBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCwinmmBackendFactory, ALCbackendFactory) } }
-
-static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory *self);
-static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory *self);
-static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory *self, ALCbackend_Type type);
-static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory *self, enum DevProbe type);
-static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwinmmBackendFactory);
-
-
-static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory* UNUSED(self))
-{
-    VECTOR_INIT(PlaybackDevices);
-    VECTOR_INIT(CaptureDevices);
-
-    return ALC_TRUE;
-}
-
-static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory* UNUSED(self))
-{
-    clear_devlist(&PlaybackDevices);
-    VECTOR_DEINIT(PlaybackDevices);
-
-    clear_devlist(&CaptureDevices);
-    VECTOR_DEINIT(CaptureDevices);
-}
-
-static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback || type == ALCbackend_Capture)
-        return ALC_TRUE;
-    return ALC_FALSE;
-}
-
-static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory* UNUSED(self), enum DevProbe type)
-{
-    switch(type)
-    {
-        case ALL_DEVICE_PROBE:
-            ProbePlaybackDevices();
-            VECTOR_FOR_EACH(const al_string, PlaybackDevices, AppendAllDevicesList2);
-            break;
-
-        case CAPTURE_DEVICE_PROBE:
-            ProbeCaptureDevices();
-            VECTOR_FOR_EACH(const al_string, CaptureDevices, AppendCaptureDeviceList2);
-            break;
-    }
-}
-
-static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
-    if(type == ALCbackend_Playback)
-    {
-        ALCwinmmPlayback *backend;
-        NEW_OBJ(backend, ALCwinmmPlayback)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-    if(type == ALCbackend_Capture)
-    {
-        ALCwinmmCapture *backend;
-        NEW_OBJ(backend, ALCwinmmCapture)(device);
-        if(!backend) return NULL;
-        return STATIC_CAST(ALCbackend, backend);
-    }
-
-    return NULL;
-}
-
-ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void)
-{
-    static ALCwinmmBackendFactory factory = ALCWINMMBACKENDFACTORY_INITIALIZER;
-    return STATIC_CAST(ALCbackendFactory, &factory);
-}

+ 631 - 0
Engine/lib/openal-soft/Alc/backends/winmm.cpp

@@ -0,0 +1,631 @@
+/**
+ * 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 "backends/winmm.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+#include <array>
+#include <atomic>
+#include <thread>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "compat.h"
+#include "core/logging.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+
+#ifndef WAVE_FORMAT_IEEE_FLOAT
+#define WAVE_FORMAT_IEEE_FLOAT  0x0003
+#endif
+
+namespace {
+
+#define DEVNAME_HEAD "OpenAL Soft on "
+
+
+al::vector<std::string> PlaybackDevices;
+al::vector<std::string> CaptureDevices;
+
+bool checkName(const al::vector<std::string> &list, const std::string &name)
+{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
+
+void ProbePlaybackDevices(void)
+{
+    PlaybackDevices.clear();
+
+    UINT numdevs{waveOutGetNumDevs()};
+    PlaybackDevices.reserve(numdevs);
+    for(UINT i{0};i < numdevs;++i)
+    {
+        std::string dname;
+
+        WAVEOUTCAPSW WaveCaps{};
+        if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
+        {
+            const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
+
+            int count{1};
+            std::string newname{basename};
+            while(checkName(PlaybackDevices, newname))
+            {
+                newname = basename;
+                newname += " #";
+                newname += std::to_string(++count);
+            }
+            dname = std::move(newname);
+
+            TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
+        }
+        PlaybackDevices.emplace_back(std::move(dname));
+    }
+}
+
+void ProbeCaptureDevices(void)
+{
+    CaptureDevices.clear();
+
+    UINT numdevs{waveInGetNumDevs()};
+    CaptureDevices.reserve(numdevs);
+    for(UINT i{0};i < numdevs;++i)
+    {
+        std::string dname;
+
+        WAVEINCAPSW WaveCaps{};
+        if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
+        {
+            const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
+
+            int count{1};
+            std::string newname{basename};
+            while(checkName(CaptureDevices, newname))
+            {
+                newname = basename;
+                newname += " #";
+                newname += std::to_string(++count);
+            }
+            dname = std::move(newname);
+
+            TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
+        }
+        CaptureDevices.emplace_back(std::move(dname));
+    }
+}
+
+
+struct WinMMPlayback final : public BackendBase {
+    WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~WinMMPlayback() override;
+
+    void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
+    static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
+    { reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
+
+    int mixerProc();
+
+    void open(const char *name) override;
+    bool reset() override;
+    void start() override;
+    void stop() override;
+
+    std::atomic<uint> mWritable{0u};
+    al::semaphore mSem;
+    uint mIdx{0u};
+    std::array<WAVEHDR,4> mWaveBuffer{};
+
+    HWAVEOUT mOutHdl{nullptr};
+
+    WAVEFORMATEX mFormat{};
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(WinMMPlayback)
+};
+
+WinMMPlayback::~WinMMPlayback()
+{
+    if(mOutHdl)
+        waveOutClose(mOutHdl);
+    mOutHdl = nullptr;
+
+    al_free(mWaveBuffer[0].lpData);
+    std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
+}
+
+/* WinMMPlayback::waveOutProc
+ *
+ * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
+ * completed and returns to the application (for more data)
+ */
+void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
+{
+    if(msg != WOM_DONE) return;
+    mWritable.fetch_add(1, std::memory_order_acq_rel);
+    mSem.post();
+}
+
+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))
+    {
+        uint todo{mWritable.load(std::memory_order_acquire)};
+        if(todo < 1)
+        {
+            mSem.wait();
+            continue;
+        }
+
+        size_t widx{mIdx};
+        do {
+            WAVEHDR &waveHdr = mWaveBuffer[widx];
+            widx = (widx+1) % mWaveBuffer.size();
+
+            mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, frame_step);
+            mWritable.fetch_sub(1, std::memory_order_acq_rel);
+            waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
+        } while(--todo);
+        mIdx = static_cast<uint>(widx);
+    }
+
+    return 0;
+}
+
+
+void WinMMPlayback::open(const char *name)
+{
+    if(PlaybackDevices.empty())
+        ProbePlaybackDevices();
+
+    // Find the Device ID matching the deviceName if valid
+    auto iter = name ?
+        std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
+        PlaybackDevices.cbegin();
+    if(iter == PlaybackDevices.cend())
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+    auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
+
+retry_open:
+    mFormat = WAVEFORMATEX{};
+    if(mDevice->FmtType == DevFmtFloat)
+    {
+        mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+        mFormat.wBitsPerSample = 32;
+    }
+    else
+    {
+        mFormat.wFormatTag = WAVE_FORMAT_PCM;
+        if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
+            mFormat.wBitsPerSample = 8;
+        else
+            mFormat.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,
+        reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
+        reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
+    if(res != MMSYSERR_NOERROR)
+    {
+        if(mDevice->FmtType == DevFmtFloat)
+        {
+            mDevice->FmtType = DevFmtShort;
+            goto retry_open;
+        }
+        throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
+    }
+
+    mDevice->DeviceName = PlaybackDevices[DeviceID];
+}
+
+bool WinMMPlayback::reset()
+{
+    mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
+        mFormat.nSamplesPerSec / mDevice->Frequency);
+    mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
+    mDevice->UpdateSize = mDevice->BufferSize / 4;
+    mDevice->Frequency = mFormat.nSamplesPerSec;
+
+    if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
+    {
+        if(mFormat.wBitsPerSample == 32)
+            mDevice->FmtType = DevFmtFloat;
+        else
+        {
+            ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
+            return false;
+        }
+    }
+    else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
+    {
+        if(mFormat.wBitsPerSample == 16)
+            mDevice->FmtType = DevFmtShort;
+        else if(mFormat.wBitsPerSample == 8)
+            mDevice->FmtType = DevFmtUByte;
+        else
+        {
+            ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
+            return false;
+        }
+    }
+    else
+    {
+        ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
+        return false;
+    }
+
+    uint chanmask{};
+    if(mFormat.nChannels == 2)
+    {
+        mDevice->FmtChans = DevFmtStereo;
+        chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+    }
+    else if(mFormat.nChannels == 1)
+    {
+        mDevice->FmtChans = DevFmtMono;
+        chanmask = SPEAKER_FRONT_CENTER;
+    }
+    else
+    {
+        ERR("Unhandled channel count: %d\n", mFormat.nChannels);
+        return false;
+    }
+    setChannelOrderFromWFXMask(chanmask);
+
+    uint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
+
+    al_free(mWaveBuffer[0].lpData);
+    mWaveBuffer[0] = WAVEHDR{};
+    mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
+    mWaveBuffer[0].dwBufferLength = BufferSize;
+    for(size_t i{1};i < mWaveBuffer.size();i++)
+    {
+        mWaveBuffer[i] = WAVEHDR{};
+        mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
+        mWaveBuffer[i].dwBufferLength = BufferSize;
+    }
+    mIdx = 0;
+
+    return true;
+}
+
+void WinMMPlayback::start()
+{
+    try {
+        std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
+            [this](WAVEHDR &waveHdr) -> void
+            { waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); });
+        mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
+
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start mixing thread: %s", e.what()};
+    }
+}
+
+void WinMMPlayback::stop()
+{
+    if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+        return;
+    mThread.join();
+
+    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)); });
+    mWritable.store(0, std::memory_order_release);
+}
+
+
+struct WinMMCapture final : public BackendBase {
+    WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+    ~WinMMCapture() override;
+
+    void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
+    static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
+    { reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
+
+    int captureProc();
+
+    void open(const char *name) override;
+    void start() override;
+    void stop() override;
+    void captureSamples(al::byte *buffer, uint samples) override;
+    uint availableSamples() override;
+
+    std::atomic<uint> mReadable{0u};
+    al::semaphore mSem;
+    uint mIdx{0};
+    std::array<WAVEHDR,4> mWaveBuffer{};
+
+    HWAVEIN mInHdl{nullptr};
+
+    RingBufferPtr mRing{nullptr};
+
+    WAVEFORMATEX mFormat{};
+
+    std::atomic<bool> mKillNow{true};
+    std::thread mThread;
+
+    DEF_NEWDEL(WinMMCapture)
+};
+
+WinMMCapture::~WinMMCapture()
+{
+    // Close the Wave device
+    if(mInHdl)
+        waveInClose(mInHdl);
+    mInHdl = nullptr;
+
+    al_free(mWaveBuffer[0].lpData);
+    std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
+}
+
+/* WinMMCapture::waveInProc
+ *
+ * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
+ * completed and returns to the application (with more data).
+ */
+void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
+{
+    if(msg != WIM_DATA) return;
+    mReadable.fetch_add(1, std::memory_order_acq_rel);
+    mSem.post();
+}
+
+int WinMMCapture::captureProc()
+{
+    althrd_setname(RECORD_THREAD_NAME);
+
+    while(!mKillNow.load(std::memory_order_acquire) &&
+          mDevice->Connected.load(std::memory_order_acquire))
+    {
+        uint todo{mReadable.load(std::memory_order_acquire)};
+        if(todo < 1)
+        {
+            mSem.wait();
+            continue;
+        }
+
+        size_t widx{mIdx};
+        do {
+            WAVEHDR &waveHdr = mWaveBuffer[widx];
+            widx = (widx+1) % mWaveBuffer.size();
+
+            mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
+            mReadable.fetch_sub(1, std::memory_order_acq_rel);
+            waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
+        } while(--todo);
+        mIdx = static_cast<uint>(widx);
+    }
+
+    return 0;
+}
+
+
+void WinMMCapture::open(const char *name)
+{
+    if(CaptureDevices.empty())
+        ProbeCaptureDevices();
+
+    // Find the Device ID matching the deviceName if valid
+    auto iter = name ?
+        std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
+        CaptureDevices.cbegin();
+    if(iter == CaptureDevices.cend())
+        throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
+            name};
+    auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
+
+    switch(mDevice->FmtChans)
+    {
+    case DevFmtMono:
+    case DevFmtStereo:
+        break;
+
+    case DevFmtQuad:
+    case DevFmtX51:
+    case DevFmtX51Rear:
+    case DevFmtX61:
+    case DevFmtX71:
+    case DevFmtAmbi3D:
+        throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
+            DevFmtChannelsString(mDevice->FmtChans)};
+    }
+
+    switch(mDevice->FmtType)
+    {
+    case DevFmtUByte:
+    case DevFmtShort:
+    case DevFmtInt:
+    case DevFmtFloat:
+        break;
+
+    case DevFmtByte:
+    case DevFmtUShort:
+    case DevFmtUInt:
+        throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
+            DevFmtTypeString(mDevice->FmtType)};
+    }
+
+    mFormat = WAVEFORMATEX{};
+    mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
+                         WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
+    mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
+    mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
+    mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
+    mFormat.nSamplesPerSec = mDevice->Frequency;
+    mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
+    mFormat.cbSize = 0;
+
+    MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
+        reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
+        reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
+    if(res != MMSYSERR_NOERROR)
+        throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
+
+    // Ensure each buffer is 50ms each
+    DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
+    BufferSize -= (BufferSize % mFormat.nBlockAlign);
+
+    // Allocate circular memory buffer for the captured audio
+    // Make sure circular buffer is at least 100ms in size
+    uint CapturedDataSize{mDevice->BufferSize};
+    CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
+
+    mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
+
+    al_free(mWaveBuffer[0].lpData);
+    mWaveBuffer[0] = WAVEHDR{};
+    mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
+    mWaveBuffer[0].dwBufferLength = BufferSize;
+    for(size_t i{1};i < mWaveBuffer.size();++i)
+    {
+        mWaveBuffer[i] = WAVEHDR{};
+        mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
+        mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
+    }
+
+    mDevice->DeviceName = CaptureDevices[DeviceID];
+}
+
+void WinMMCapture::start()
+{
+    try {
+        for(size_t i{0};i < mWaveBuffer.size();++i)
+        {
+            waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
+            waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
+        }
+
+        mKillNow.store(false, std::memory_order_release);
+        mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
+
+        waveInStart(mInHdl);
+    }
+    catch(std::exception& e) {
+        throw al::backend_exception{al::backend_error::DeviceError,
+            "Failed to start recording thread: %s", e.what()};
+    }
+}
+
+void WinMMCapture::stop()
+{
+    waveInStop(mInHdl);
+
+    mKillNow.store(true, std::memory_order_release);
+    if(mThread.joinable())
+    {
+        mSem.post();
+        mThread.join();
+    }
+
+    waveInReset(mInHdl);
+    for(size_t i{0};i < mWaveBuffer.size();++i)
+        waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
+
+    mReadable.store(0, std::memory_order_release);
+    mIdx = 0;
+}
+
+void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
+{ mRing->read(buffer, samples); }
+
+uint WinMMCapture::availableSamples()
+{ return static_cast<uint>(mRing->readSpace()); }
+
+} // namespace
+
+
+bool WinMMBackendFactory::init()
+{ return true; }
+
+bool WinMMBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+std::string WinMMBackendFactory::probe(BackendType type)
+{
+    std::string outnames;
+    auto add_device = [&outnames](const std::string &dname) -> void
+    {
+        /* +1 to also append the null char (to ensure a null-separated list and
+         * double-null terminated list).
+         */
+        if(!dname.empty())
+            outnames.append(dname.c_str(), dname.length()+1);
+    };
+    switch(type)
+    {
+    case BackendType::Playback:
+        ProbePlaybackDevices();
+        std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+        break;
+
+    case BackendType::Capture:
+        ProbeCaptureDevices();
+        std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+        break;
+    }
+    return outnames;
+}
+
+BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+    if(type == BackendType::Playback)
+        return BackendPtr{new WinMMPlayback{device}};
+    if(type == BackendType::Capture)
+        return BackendPtr{new WinMMCapture{device}};
+    return nullptr;
+}
+
+BackendFactory &WinMMBackendFactory::getFactory()
+{
+    static WinMMBackendFactory factory{};
+    return factory;
+}

+ 19 - 0
Engine/lib/openal-soft/Alc/backends/winmm.h

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

+ 0 - 492
Engine/lib/openal-soft/Alc/bformatdec.c

@@ -1,492 +0,0 @@
-
-#include "config.h"
-
-#include "bformatdec.h"
-#include "ambdec.h"
-#include "filters/splitter.h"
-#include "alu.h"
-
-#include "bool.h"
-#include "threads.h"
-#include "almalloc.h"
-
-
-/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
- * coefficients should be divided by these values to get proper N3D scalings.
- */
-const ALfloat N3D2N3DScale[MAX_AMBI_COEFFS] = {
-    1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-    1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
-};
-const ALfloat SN3D2N3DScale[MAX_AMBI_COEFFS] = {
-    1.000000000f, /* ACN  0 (W), sqrt(1) */
-    1.732050808f, /* ACN  1 (Y), sqrt(3) */
-    1.732050808f, /* ACN  2 (Z), sqrt(3) */
-    1.732050808f, /* ACN  3 (X), sqrt(3) */
-    2.236067978f, /* ACN  4 (V), sqrt(5) */
-    2.236067978f, /* ACN  5 (T), sqrt(5) */
-    2.236067978f, /* ACN  6 (R), sqrt(5) */
-    2.236067978f, /* ACN  7 (S), sqrt(5) */
-    2.236067978f, /* ACN  8 (U), sqrt(5) */
-    2.645751311f, /* ACN  9 (Q), sqrt(7) */
-    2.645751311f, /* ACN 10 (O), sqrt(7) */
-    2.645751311f, /* ACN 11 (M), sqrt(7) */
-    2.645751311f, /* ACN 12 (K), sqrt(7) */
-    2.645751311f, /* ACN 13 (L), sqrt(7) */
-    2.645751311f, /* ACN 14 (N), sqrt(7) */
-    2.645751311f, /* ACN 15 (P), sqrt(7) */
-};
-const ALfloat FuMa2N3DScale[MAX_AMBI_COEFFS] = {
-    1.414213562f, /* ACN  0 (W), sqrt(2) */
-    1.732050808f, /* ACN  1 (Y), sqrt(3) */
-    1.732050808f, /* ACN  2 (Z), sqrt(3) */
-    1.732050808f, /* ACN  3 (X), sqrt(3) */
-    1.936491673f, /* ACN  4 (V), sqrt(15)/2 */
-    1.936491673f, /* ACN  5 (T), sqrt(15)/2 */
-    2.236067978f, /* ACN  6 (R), sqrt(5) */
-    1.936491673f, /* ACN  7 (S), sqrt(15)/2 */
-    1.936491673f, /* ACN  8 (U), sqrt(15)/2 */
-    2.091650066f, /* ACN  9 (Q), sqrt(35/8) */
-    1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
-    2.231093404f, /* ACN 11 (M), sqrt(224/45) */
-    2.645751311f, /* ACN 12 (K), sqrt(7) */
-    2.231093404f, /* ACN 13 (L), sqrt(224/45) */
-    1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
-    2.091650066f, /* ACN 15 (P), sqrt(35/8) */
-};
-
-
-#define HF_BAND 0
-#define LF_BAND 1
-#define NUM_BANDS 2
-
-/* These points are in AL coordinates! */
-static const ALfloat Ambi3DPoints[8][3] = {
-    { -0.577350269f,  0.577350269f, -0.577350269f },
-    {  0.577350269f,  0.577350269f, -0.577350269f },
-    { -0.577350269f,  0.577350269f,  0.577350269f },
-    {  0.577350269f,  0.577350269f,  0.577350269f },
-    { -0.577350269f, -0.577350269f, -0.577350269f },
-    {  0.577350269f, -0.577350269f, -0.577350269f },
-    { -0.577350269f, -0.577350269f,  0.577350269f },
-    {  0.577350269f, -0.577350269f,  0.577350269f },
-};
-static const ALfloat Ambi3DDecoder[8][MAX_AMBI_COEFFS] = {
-    { 0.125f,  0.125f,  0.125f,  0.125f },
-    { 0.125f, -0.125f,  0.125f,  0.125f },
-    { 0.125f,  0.125f,  0.125f, -0.125f },
-    { 0.125f, -0.125f,  0.125f, -0.125f },
-    { 0.125f,  0.125f, -0.125f,  0.125f },
-    { 0.125f, -0.125f, -0.125f,  0.125f },
-    { 0.125f,  0.125f, -0.125f, -0.125f },
-    { 0.125f, -0.125f, -0.125f, -0.125f },
-};
-static const ALfloat Ambi3DDecoderHFScale[MAX_AMBI_COEFFS] = {
-    2.0f,
-    1.15470054f, 1.15470054f, 1.15470054f
-};
-
-
-/* NOTE: BandSplitter filters are unused with single-band decoding */
-typedef struct BFormatDec {
-    ALuint Enabled; /* Bitfield of enabled channels. */
-
-    union {
-        alignas(16) ALfloat Dual[MAX_OUTPUT_CHANNELS][NUM_BANDS][MAX_AMBI_COEFFS];
-        alignas(16) ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS];
-    } Matrix;
-
-    BandSplitter XOver[MAX_AMBI_COEFFS];
-
-    ALfloat (*Samples)[BUFFERSIZE];
-    /* These two alias into Samples */
-    ALfloat (*SamplesHF)[BUFFERSIZE];
-    ALfloat (*SamplesLF)[BUFFERSIZE];
-
-    alignas(16) ALfloat ChannelMix[BUFFERSIZE];
-
-    struct {
-        BandSplitter XOver;
-        ALfloat Gains[NUM_BANDS];
-    } UpSampler[4];
-
-    ALsizei NumChannels;
-    ALboolean DualBand;
-} BFormatDec;
-
-BFormatDec *bformatdec_alloc()
-{
-    return al_calloc(16, sizeof(BFormatDec));
-}
-
-void bformatdec_free(BFormatDec **dec)
-{
-    if(dec && *dec)
-    {
-        al_free((*dec)->Samples);
-        (*dec)->Samples = NULL;
-        (*dec)->SamplesHF = NULL;
-        (*dec)->SamplesLF = NULL;
-
-        al_free(*dec);
-        *dec = NULL;
-    }
-}
-
-void bformatdec_reset(BFormatDec *dec, const AmbDecConf *conf, ALsizei chancount, ALuint srate, const ALsizei chanmap[MAX_OUTPUT_CHANNELS])
-{
-    static const ALsizei map2DTo3D[MAX_AMBI2D_COEFFS] = {
-        0,  1, 3,  4, 8,  9, 15
-    };
-    const ALfloat *coeff_scale = N3D2N3DScale;
-    bool periphonic;
-    ALfloat ratio;
-    ALsizei i;
-
-    al_free(dec->Samples);
-    dec->Samples = NULL;
-    dec->SamplesHF = NULL;
-    dec->SamplesLF = NULL;
-
-    dec->NumChannels = chancount;
-    dec->Samples = al_calloc(16, dec->NumChannels*2 * sizeof(dec->Samples[0]));
-    dec->SamplesHF = dec->Samples;
-    dec->SamplesLF = dec->SamplesHF + dec->NumChannels;
-
-    dec->Enabled = 0;
-    for(i = 0;i < conf->NumSpeakers;i++)
-        dec->Enabled |= 1 << chanmap[i];
-
-    if(conf->CoeffScale == ADS_SN3D)
-        coeff_scale = SN3D2N3DScale;
-    else if(conf->CoeffScale == ADS_FuMa)
-        coeff_scale = FuMa2N3DScale;
-
-    memset(dec->UpSampler, 0, sizeof(dec->UpSampler));
-    ratio = 400.0f / (ALfloat)srate;
-    for(i = 0;i < 4;i++)
-        bandsplit_init(&dec->UpSampler[i].XOver, ratio);
-    if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
-    {
-        periphonic = true;
-
-        dec->UpSampler[0].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? W_SCALE_3H3P :
-                                           (conf->ChanMask > 0xf) ? W_SCALE_2H2P : 1.0f;
-        dec->UpSampler[0].Gains[LF_BAND] = 1.0f;
-        for(i = 1;i < 4;i++)
-        {
-            dec->UpSampler[i].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? XYZ_SCALE_3H3P :
-                                               (conf->ChanMask > 0xf) ? XYZ_SCALE_2H2P : 1.0f;
-            dec->UpSampler[i].Gains[LF_BAND] = 1.0f;
-        }
-    }
-    else
-    {
-        periphonic = false;
-
-        dec->UpSampler[0].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? W_SCALE_3H0P :
-                                           (conf->ChanMask > 0xf) ? W_SCALE_2H0P : 1.0f;
-        dec->UpSampler[0].Gains[LF_BAND] = 1.0f;
-        for(i = 1;i < 3;i++)
-        {
-            dec->UpSampler[i].Gains[HF_BAND] = (conf->ChanMask > 0x1ff) ? XYZ_SCALE_3H0P :
-                                               (conf->ChanMask > 0xf) ? XYZ_SCALE_2H0P : 1.0f;
-            dec->UpSampler[i].Gains[LF_BAND] = 1.0f;
-        }
-        dec->UpSampler[3].Gains[HF_BAND] = 0.0f;
-        dec->UpSampler[3].Gains[LF_BAND] = 0.0f;
-    }
-
-    memset(&dec->Matrix, 0, sizeof(dec->Matrix));
-    if(conf->FreqBands == 1)
-    {
-        dec->DualBand = AL_FALSE;
-        for(i = 0;i < conf->NumSpeakers;i++)
-        {
-            ALsizei chan = chanmap[i];
-            ALfloat gain;
-            ALsizei j, k;
-
-            if(!periphonic)
-            {
-                for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
-                {
-                    ALsizei l = map2DTo3D[j];
-                    if(j == 0) gain = conf->HFOrderGain[0];
-                    else if(j == 1) gain = conf->HFOrderGain[1];
-                    else if(j == 3) gain = conf->HFOrderGain[2];
-                    else if(j == 5) gain = conf->HFOrderGain[3];
-                    if((conf->ChanMask&(1<<l)))
-                        dec->Matrix.Single[chan][j] = conf->HFMatrix[i][k++] / coeff_scale[l] *
-                                                      gain;
-                }
-            }
-            else
-            {
-                for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
-                {
-                    if(j == 0) gain = conf->HFOrderGain[0];
-                    else if(j == 1) gain = conf->HFOrderGain[1];
-                    else if(j == 4) gain = conf->HFOrderGain[2];
-                    else if(j == 9) gain = conf->HFOrderGain[3];
-                    if((conf->ChanMask&(1<<j)))
-                        dec->Matrix.Single[chan][j] = conf->HFMatrix[i][k++] / coeff_scale[j] *
-                                                      gain;
-                }
-            }
-        }
-    }
-    else
-    {
-        dec->DualBand = AL_TRUE;
-
-        ratio = conf->XOverFreq / (ALfloat)srate;
-        for(i = 0;i < MAX_AMBI_COEFFS;i++)
-            bandsplit_init(&dec->XOver[i], ratio);
-
-        ratio = powf(10.0f, conf->XOverRatio / 40.0f);
-        for(i = 0;i < conf->NumSpeakers;i++)
-        {
-            ALsizei chan = chanmap[i];
-            ALfloat gain;
-            ALsizei j, k;
-
-            if(!periphonic)
-            {
-                for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
-                {
-                    ALsizei l = map2DTo3D[j];
-                    if(j == 0) gain = conf->HFOrderGain[0] * ratio;
-                    else if(j == 1) gain = conf->HFOrderGain[1] * ratio;
-                    else if(j == 3) gain = conf->HFOrderGain[2] * ratio;
-                    else if(j == 5) gain = conf->HFOrderGain[3] * ratio;
-                    if((conf->ChanMask&(1<<l)))
-                        dec->Matrix.Dual[chan][HF_BAND][j] = conf->HFMatrix[i][k++] /
-                                                             coeff_scale[l] * gain;
-                }
-                for(j = 0,k = 0;j < MAX_AMBI2D_COEFFS;j++)
-                {
-                    ALsizei l = map2DTo3D[j];
-                    if(j == 0) gain = conf->LFOrderGain[0] / ratio;
-                    else if(j == 1) gain = conf->LFOrderGain[1] / ratio;
-                    else if(j == 3) gain = conf->LFOrderGain[2] / ratio;
-                    else if(j == 5) gain = conf->LFOrderGain[3] / ratio;
-                    if((conf->ChanMask&(1<<l)))
-                        dec->Matrix.Dual[chan][LF_BAND][j] = conf->LFMatrix[i][k++] /
-                                                             coeff_scale[l] * gain;
-                }
-            }
-            else
-            {
-                for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
-                {
-                    if(j == 0) gain = conf->HFOrderGain[0] * ratio;
-                    else if(j == 1) gain = conf->HFOrderGain[1] * ratio;
-                    else if(j == 4) gain = conf->HFOrderGain[2] * ratio;
-                    else if(j == 9) gain = conf->HFOrderGain[3] * ratio;
-                    if((conf->ChanMask&(1<<j)))
-                        dec->Matrix.Dual[chan][HF_BAND][j] = conf->HFMatrix[i][k++] /
-                                                             coeff_scale[j] * gain;
-                }
-                for(j = 0,k = 0;j < MAX_AMBI_COEFFS;j++)
-                {
-                    if(j == 0) gain = conf->LFOrderGain[0] / ratio;
-                    else if(j == 1) gain = conf->LFOrderGain[1] / ratio;
-                    else if(j == 4) gain = conf->LFOrderGain[2] / ratio;
-                    else if(j == 9) gain = conf->LFOrderGain[3] / ratio;
-                    if((conf->ChanMask&(1<<j)))
-                        dec->Matrix.Dual[chan][LF_BAND][j] = conf->LFMatrix[i][k++] /
-                                                             coeff_scale[j] * gain;
-                }
-            }
-        }
-    }
-}
-
-
-void bformatdec_process(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
-{
-    ALsizei chan, i;
-
-    OutBuffer = ASSUME_ALIGNED(OutBuffer, 16);
-    if(dec->DualBand)
-    {
-        for(i = 0;i < dec->NumChannels;i++)
-            bandsplit_process(&dec->XOver[i], dec->SamplesHF[i], dec->SamplesLF[i],
-                              InSamples[i], SamplesToDo);
-
-        for(chan = 0;chan < OutChannels;chan++)
-        {
-            if(!(dec->Enabled&(1<<chan)))
-                continue;
-
-            memset(dec->ChannelMix, 0, SamplesToDo*sizeof(ALfloat));
-            MixRowSamples(dec->ChannelMix, dec->Matrix.Dual[chan][HF_BAND],
-                dec->SamplesHF, dec->NumChannels, 0, SamplesToDo
-            );
-            MixRowSamples(dec->ChannelMix, dec->Matrix.Dual[chan][LF_BAND],
-                dec->SamplesLF, dec->NumChannels, 0, SamplesToDo
-            );
-
-            for(i = 0;i < SamplesToDo;i++)
-                OutBuffer[chan][i] += dec->ChannelMix[i];
-        }
-    }
-    else
-    {
-        for(chan = 0;chan < OutChannels;chan++)
-        {
-            if(!(dec->Enabled&(1<<chan)))
-                continue;
-
-            memset(dec->ChannelMix, 0, SamplesToDo*sizeof(ALfloat));
-            MixRowSamples(dec->ChannelMix, dec->Matrix.Single[chan], InSamples,
-                          dec->NumChannels, 0, SamplesToDo);
-
-            for(i = 0;i < SamplesToDo;i++)
-                OutBuffer[chan][i] += dec->ChannelMix[i];
-        }
-    }
-}
-
-
-void bformatdec_upSample(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei InChannels, ALsizei SamplesToDo)
-{
-    ALsizei i;
-
-    /* This up-sampler leverages the differences observed in dual-band second-
-     * and third-order decoder matrices compared to first-order. For the same
-     * output channel configuration, the low-frequency matrix has identical
-     * coefficients in the shared input channels, while the high-frequency
-     * matrix has extra scalars applied to the W channel and X/Y/Z channels.
-     * Mixing the first-order content into the higher-order stream with the
-     * appropriate counter-scales applied to the HF response results in the
-     * subsequent higher-order decode generating the same response as a first-
-     * order decode.
-     */
-    for(i = 0;i < InChannels;i++)
-    {
-        /* First, split the first-order components into low and high frequency
-         * bands.
-         */
-        bandsplit_process(&dec->UpSampler[i].XOver,
-            dec->Samples[HF_BAND], dec->Samples[LF_BAND],
-            InSamples[i], SamplesToDo
-        );
-
-        /* Now write each band to the output. */
-        MixRowSamples(OutBuffer[i], dec->UpSampler[i].Gains,
-            dec->Samples, NUM_BANDS, 0, SamplesToDo
-        );
-    }
-}
-
-
-#define INVALID_UPSAMPLE_INDEX INT_MAX
-
-static ALsizei GetACNIndex(const BFChannelConfig *chans, ALsizei numchans, ALsizei acn)
-{
-    ALsizei i;
-    for(i = 0;i < numchans;i++)
-    {
-        if(chans[i].Index == acn)
-            return i;
-    }
-    return INVALID_UPSAMPLE_INDEX;
-}
-#define GetChannelForACN(b, a) GetACNIndex((b).Ambi.Map, (b).NumChannels, (a))
-
-typedef struct AmbiUpsampler {
-    alignas(16) ALfloat Samples[NUM_BANDS][BUFFERSIZE];
-
-    BandSplitter XOver[4];
-
-    ALfloat Gains[4][MAX_OUTPUT_CHANNELS][NUM_BANDS];
-} AmbiUpsampler;
-
-AmbiUpsampler *ambiup_alloc()
-{
-    return al_calloc(16, sizeof(AmbiUpsampler));
-}
-
-void ambiup_free(struct AmbiUpsampler **ambiup)
-{
-    if(ambiup)
-    {
-        al_free(*ambiup);
-        *ambiup = NULL;
-    }
-}
-
-void ambiup_reset(struct AmbiUpsampler *ambiup, const ALCdevice *device, ALfloat w_scale, ALfloat xyz_scale)
-{
-    ALfloat ratio;
-    ALsizei i;
-
-    ratio = 400.0f / (ALfloat)device->Frequency;
-    for(i = 0;i < 4;i++)
-        bandsplit_init(&ambiup->XOver[i], ratio);
-
-    memset(ambiup->Gains, 0, sizeof(ambiup->Gains));
-    if(device->Dry.CoeffCount > 0)
-    {
-        ALfloat encgains[8][MAX_OUTPUT_CHANNELS];
-        ALsizei j;
-        size_t k;
-
-        for(k = 0;k < COUNTOF(Ambi3DPoints);k++)
-        {
-            ALfloat coeffs[MAX_AMBI_COEFFS] = { 0.0f };
-            CalcDirectionCoeffs(Ambi3DPoints[k], 0.0f, coeffs);
-            ComputeDryPanGains(&device->Dry, coeffs, 1.0f, encgains[k]);
-        }
-
-        /* Combine the matrices that do the in->virt and virt->out conversions
-         * so we get a single in->out conversion. NOTE: the Encoder matrix
-         * (encgains) and output are transposed, so the input channels line up
-         * with the rows and the output channels line up with the columns.
-         */
-        for(i = 0;i < 4;i++)
-        {
-            for(j = 0;j < device->Dry.NumChannels;j++)
-            {
-                ALfloat gain=0.0f;
-                for(k = 0;k < COUNTOF(Ambi3DDecoder);k++)
-                    gain += Ambi3DDecoder[k][i] * encgains[k][j];
-                ambiup->Gains[i][j][HF_BAND] = gain * Ambi3DDecoderHFScale[i];
-                ambiup->Gains[i][j][LF_BAND] = gain;
-            }
-        }
-    }
-    else
-    {
-        for(i = 0;i < 4;i++)
-        {
-            ALsizei index = GetChannelForACN(device->Dry, i);
-            if(index != INVALID_UPSAMPLE_INDEX)
-            {
-                ALfloat scale = device->Dry.Ambi.Map[index].Scale;
-                ambiup->Gains[i][index][HF_BAND] = scale * ((i==0) ? w_scale : xyz_scale);
-                ambiup->Gains[i][index][LF_BAND] = scale;
-            }
-        }
-    }
-}
-
-void ambiup_process(struct AmbiUpsampler *ambiup, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
-{
-    ALsizei i, j;
-
-    for(i = 0;i < 4;i++)
-    {
-        bandsplit_process(&ambiup->XOver[i],
-            ambiup->Samples[HF_BAND], ambiup->Samples[LF_BAND],
-            InSamples[i], SamplesToDo
-        );
-
-        for(j = 0;j < OutChannels;j++)
-            MixRowSamples(OutBuffer[j], ambiup->Gains[i][j],
-                ambiup->Samples, NUM_BANDS, 0, SamplesToDo
-            );
-    }
-}

+ 298 - 0
Engine/lib/openal-soft/Alc/bformatdec.cpp

@@ -0,0 +1,298 @@
+
+#include "config.h"
+
+#include "bformatdec.h"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cmath>
+#include <iterator>
+#include <numeric>
+
+#include "almalloc.h"
+#include "alu.h"
+#include "core/ambdec.h"
+#include "core/filters/splitter.h"
+#include "front_stablizer.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+
+
+namespace {
+
+constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale{{
+    1.00000000e+00f, 1.00000000e+00f
+}};
+constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale2O{{
+    7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
+}};
+constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale3O{{
+    5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
+}};
+
+inline auto& GetDecoderHFScales(uint order) noexcept
+{
+    if(order >= 3) return Ambi3DDecoderHFScale3O;
+    if(order == 2) return Ambi3DDecoderHFScale2O;
+    return Ambi3DDecoderHFScale;
+}
+
+inline auto& GetAmbiScales(AmbDecScale scaletype) noexcept
+{
+    if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa();
+    if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D();
+    return AmbiScale::FromN3D();
+}
+
+} // namespace
+
+
+BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const size_t inchans,
+    const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
+    std::unique_ptr<FrontStablizer> stablizer)
+    : mStablizer{std::move(stablizer)}, mDualBand{allow_2band && (conf->FreqBands == 2)}
+    , mChannelDec{inchans}
+{
+    const bool periphonic{(conf->ChanMask&AmbiPeriphonicMask) != 0};
+    auto&& coeff_scale = GetAmbiScales(conf->CoeffScale);
+
+    if(!mDualBand)
+    {
+        for(size_t j{0},k{0};j < mChannelDec.size();++j)
+        {
+            const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]};
+            if(!(conf->ChanMask&(1u<<acn))) continue;
+            const size_t order{AmbiIndex::OrderFromChannel()[acn]};
+            const float gain{conf->HFOrderGain[order] / coeff_scale[acn]};
+            for(size_t i{0u};i < conf->NumSpeakers;++i)
+            {
+                const size_t chanidx{chanmap[i]};
+                mChannelDec[j].mGains.Single[chanidx] = conf->Matrix[i][k] * gain;
+            }
+            ++k;
+        }
+    }
+    else
+    {
+        mChannelDec[0].mXOver.init(conf->XOverFreq / static_cast<float>(srate));
+        for(size_t j{1};j < mChannelDec.size();++j)
+            mChannelDec[j].mXOver = mChannelDec[0].mXOver;
+
+        const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
+        for(size_t j{0},k{0};j < mChannelDec.size();++j)
+        {
+            const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]};
+            if(!(conf->ChanMask&(1u<<acn))) continue;
+            const size_t order{AmbiIndex::OrderFromChannel()[acn]};
+            const float hfGain{conf->HFOrderGain[order] * ratio / coeff_scale[acn]};
+            const float lfGain{conf->LFOrderGain[order] / ratio / coeff_scale[acn]};
+            for(size_t i{0u};i < conf->NumSpeakers;++i)
+            {
+                const size_t chanidx{chanmap[i]};
+                mChannelDec[j].mGains.Dual[sHFBand][chanidx] = conf->HFMatrix[i][k] * hfGain;
+                mChannelDec[j].mGains.Dual[sLFBand][chanidx] = conf->LFMatrix[i][k] * lfGain;
+            }
+            ++k;
+        }
+    }
+}
+
+BFormatDec::BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
+    const al::span<const ChannelDec> coeffslf, std::unique_ptr<FrontStablizer> stablizer)
+    : mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans}
+{
+    if(!mDualBand)
+    {
+        for(size_t j{0};j < mChannelDec.size();++j)
+        {
+            float *outcoeffs{mChannelDec[j].mGains.Single};
+            for(const ChannelDec &incoeffs : coeffs)
+                *(outcoeffs++) = incoeffs[j];
+        }
+    }
+    else
+    {
+        for(size_t j{0};j < mChannelDec.size();++j)
+        {
+            float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]};
+            for(const ChannelDec &incoeffs : coeffs)
+                *(outcoeffs++) = incoeffs[j];
+
+            outcoeffs = mChannelDec[j].mGains.Dual[sLFBand];
+            for(const ChannelDec &incoeffs : coeffslf)
+                *(outcoeffs++) = incoeffs[j];
+        }
+    }
+}
+
+
+void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
+    const FloatBufferLine *InSamples, const size_t SamplesToDo)
+{
+    ASSUME(SamplesToDo > 0);
+
+    if(mDualBand)
+    {
+        const al::span<float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
+        const al::span<float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
+        for(auto &chandec : mChannelDec)
+        {
+            chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(),
+                lfSamples.data());
+            MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand],
+                chandec.mGains.Dual[sHFBand], 0, 0);
+            MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand],
+                chandec.mGains.Dual[sLFBand], 0, 0);
+            ++InSamples;
+        }
+    }
+    else
+    {
+        for(auto &chandec : mChannelDec)
+        {
+            MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
+                chandec.mGains.Single, 0, 0);
+            ++InSamples;
+        }
+    }
+}
+
+void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
+    const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
+    const size_t SamplesToDo)
+{
+    ASSUME(SamplesToDo > 0);
+
+    /* Move the existing direct L/R signal out so it doesn't get processed by
+     * the stablizer. Add a delay to it so it stays aligned with the stablizer
+     * delay.
+     */
+    float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())};
+    float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())};
+    for(size_t i{0};i < SamplesToDo;++i)
+    {
+        mid[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
+        side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i];
+    }
+    std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f);
+    std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f);
+
+    /* Decode the B-Format input to OutBuffer. */
+    process(OutBuffer, InSamples, SamplesToDo);
+
+    /* Apply a delay to all channels, except the front-left and front-right, so
+     * they maintain correct timing.
+     */
+    const size_t NumChannels{OutBuffer.size()};
+    for(size_t i{0u};i < NumChannels;i++)
+    {
+        if(i == lidx || i == ridx)
+            continue;
+
+        auto &DelayBuf = mStablizer->DelayBuf[i];
+        auto buffer_end = OutBuffer[i].begin() + SamplesToDo;
+        if LIKELY(SamplesToDo >= FrontStablizer::DelayLength)
+        {
+            auto delay_end = std::rotate(OutBuffer[i].begin(),
+                buffer_end - FrontStablizer::DelayLength, buffer_end);
+            std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin());
+        }
+        else
+        {
+            auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end,
+                DelayBuf.begin());
+            std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end());
+        }
+    }
+
+    /* Include the side signal for what was just decoded. */
+    for(size_t i{0};i < SamplesToDo;++i)
+        side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
+
+    /* Combine the delayed mid signal with the decoded mid signal. Note that
+     * the samples are stored and combined in reverse, so the newest samples
+     * are at the front and the oldest at the back.
+     */
+    al::span<float> tmpbuf{mStablizer->TempBuf.data(), SamplesToDo+FrontStablizer::DelayLength};
+    auto tmpiter = tmpbuf.begin() + SamplesToDo;
+    std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpiter);
+    for(size_t i{0};i < SamplesToDo;++i)
+        *--tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i];
+    /* Save the newest samples for next time. */
+    std::copy_n(tmpbuf.cbegin(), mStablizer->MidDelay.size(), mStablizer->MidDelay.begin());
+
+    /* Apply an all-pass on the reversed signal, then reverse the samples to
+     * get the forward signal with a reversed phase shift. The future samples
+     * are included with the all-pass to reduce the error in the output
+     * samples (the smaller the delay, the more error is introduced).
+     */
+    mStablizer->MidFilter.applyAllpass(tmpbuf);
+    tmpbuf = tmpbuf.subspan<FrontStablizer::DelayLength>();
+    std::reverse(tmpbuf.begin(), tmpbuf.end());
+
+    /* Now apply the band-splitter, combining its phase shift with the reversed
+     * phase shift, restoring the original phase on the split signal.
+     */
+    mStablizer->MidFilter.process(tmpbuf, mStablizer->MidHF.data(), mStablizer->MidLF.data());
+
+    /* This pans the separate low- and high-frequency signals between being on
+     * the center channel and the left+right channels. The low-frequency signal
+     * is panned 1/3rd toward center and the high-frequency signal is panned
+     * 1/4th toward center. These values can be tweaked.
+     */
+    const float cos_lf{std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f))};
+    const float cos_hf{std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
+    const float sin_lf{std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f))};
+    const float sin_hf{std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
+    for(size_t i{0};i < SamplesToDo;i++)
+    {
+        const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]};
+        const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf};
+        const float s{side[i]};
+
+        /* The generated center channel signal adds to the existing signal,
+         * while the modified left and right channels replace.
+         */
+        OutBuffer[lidx][i] = (m + s) * 0.5f;
+        OutBuffer[ridx][i] = (m - s) * 0.5f;
+        OutBuffer[cidx][i] += c * 0.5f;
+    }
+    /* Move the delayed mid/side samples to the front for next time. */
+    auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo;
+    std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin());
+    auto side_end = mStablizer->Side.cbegin() + SamplesToDo;
+    std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin());
+}
+
+
+auto BFormatDec::GetHFOrderScales(const uint in_order, const uint out_order) noexcept
+    -> std::array<float,MaxAmbiOrder+1>
+{
+    std::array<float,MaxAmbiOrder+1> ret{};
+
+    assert(out_order >= in_order);
+
+    const auto &target = GetDecoderHFScales(out_order);
+    const auto &input = GetDecoderHFScales(in_order);
+
+    for(size_t i{0};i < in_order+1;++i)
+        ret[i] = input[i] / target[i];
+
+    return ret;
+}
+
+std::unique_ptr<BFormatDec> BFormatDec::Create(const AmbDecConf *conf, const bool allow_2band,
+    const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
+    std::unique_ptr<FrontStablizer> stablizer)
+{
+    return std::unique_ptr<BFormatDec>{new(FamCount(inchans))
+        BFormatDec{conf, allow_2band, inchans, srate, chanmap, std::move(stablizer)}};
+}
+std::unique_ptr<BFormatDec> BFormatDec::Create(const size_t inchans,
+    const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
+    std::unique_ptr<FrontStablizer> stablizer)
+{
+    return std::unique_ptr<BFormatDec>{new(FamCount(inchans))
+        BFormatDec{inchans, coeffs, coeffslf, std::move(stablizer)}};
+}

+ 56 - 38
Engine/lib/openal-soft/Alc/bformatdec.h

@@ -1,57 +1,75 @@
 #ifndef BFORMATDEC_H
 #define BFORMATDEC_H
 
-#include "alMain.h"
+#include <array>
+#include <cstddef>
+#include <memory>
 
+#include "almalloc.h"
+#include "alspan.h"
+#include "core/ambidefs.h"
+#include "core/bufferline.h"
+#include "core/devformat.h"
+#include "core/filters/splitter.h"
 
-/* These are the necessary scales for first-order HF responses to play over
- * higher-order 2D (non-periphonic) decoders.
- */
-#define W_SCALE_2H0P   1.224744871f /* sqrt(1.5) */
-#define XYZ_SCALE_2H0P 1.0f
-#define W_SCALE_3H0P   1.414213562f /* sqrt(2) */
-#define XYZ_SCALE_3H0P 1.082392196f
+struct AmbDecConf;
+struct FrontStablizer;
 
-/* These are the necessary scales for first-order HF responses to play over
- * higher-order 3D (periphonic) decoders.
- */
-#define W_SCALE_2H2P   1.341640787f /* sqrt(1.8) */
-#define XYZ_SCALE_2H2P 1.0f
-#define W_SCALE_3H3P   1.695486018f
-#define XYZ_SCALE_3H3P 1.136697713f
 
+using ChannelDec = std::array<float,MaxAmbiChannels>;
 
-/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
- * coefficients should be divided by these values to get proper N3D scalings.
- */
-const ALfloat N3D2N3DScale[MAX_AMBI_COEFFS];
-const ALfloat SN3D2N3DScale[MAX_AMBI_COEFFS];
-const ALfloat FuMa2N3DScale[MAX_AMBI_COEFFS];
+class BFormatDec {
+    static constexpr size_t sHFBand{0};
+    static constexpr size_t sLFBand{1};
+    static constexpr size_t sNumBands{2};
 
+    struct ChannelDecoder {
+        union MatrixU {
+            float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
+            float Single[MAX_OUTPUT_CHANNELS];
+        } mGains{};
 
-struct AmbDecConf;
-struct BFormatDec;
-struct AmbiUpsampler;
+        /* NOTE: BandSplitter filter is unused with single-band decoding. */
+        BandSplitter mXOver;
+    };
+
+    alignas(16) std::array<FloatBufferLine,2> mSamples;
+
+    const std::unique_ptr<FrontStablizer> mStablizer;
+    const bool mDualBand{false};
+
+    al::FlexArray<ChannelDecoder> mChannelDec;
 
+public:
+    BFormatDec(const AmbDecConf *conf, const bool allow_2band, const size_t inchans,
+        const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
+        std::unique_ptr<FrontStablizer> stablizer);
+    BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
+        const al::span<const ChannelDec> coeffslf, std::unique_ptr<FrontStablizer> stablizer);
 
-struct BFormatDec *bformatdec_alloc();
-void bformatdec_free(struct BFormatDec **dec);
-void bformatdec_reset(struct BFormatDec *dec, const struct AmbDecConf *conf, ALsizei chancount, ALuint srate, const ALsizei chanmap[MAX_OUTPUT_CHANNELS]);
+    bool hasStablizer() const noexcept { return mStablizer != nullptr; };
 
-/* Decodes the ambisonic input to the given output channels. */
-void bformatdec_process(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
+    /* Decodes the ambisonic input to the given output channels. */
+    void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
+        const size_t SamplesToDo);
 
-/* Up-samples a first-order input to the decoder's configuration. */
-void bformatdec_upSample(struct BFormatDec *dec, ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei InChannels, ALsizei SamplesToDo);
+    /* Decodes the ambisonic input to the given output channels with stablization. */
+    void processStablize(const al::span<FloatBufferLine> OutBuffer,
+        const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
+        const size_t SamplesToDo);
 
+    /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
+    static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint in_order,
+        const uint out_order) noexcept;
 
-/* Stand-alone first-order upsampler. Kept here because it shares some stuff
- * with bformatdec. Assumes a periphonic (4-channel) input mix!
- */
-struct AmbiUpsampler *ambiup_alloc();
-void ambiup_free(struct AmbiUpsampler **ambiup);
-void ambiup_reset(struct AmbiUpsampler *ambiup, const ALCdevice *device, ALfloat w_scale, ALfloat xyz_scale);
+    static std::unique_ptr<BFormatDec> Create(const AmbDecConf *conf, const bool allow_2band,
+        const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
+        std::unique_ptr<FrontStablizer> stablizer);
+    static std::unique_ptr<BFormatDec> Create(const size_t inchans,
+        const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
+        std::unique_ptr<FrontStablizer> stablizer);
 
-void ambiup_process(struct AmbiUpsampler *ambiup, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
+    DEF_FAM_NEWDEL(BFormatDec, mChannelDec)
+};
 
 #endif /* BFORMATDEC_H */

+ 38 - 0
Engine/lib/openal-soft/Alc/buffer_storage.cpp

@@ -0,0 +1,38 @@
+
+#include "config.h"
+
+#include "buffer_storage.h"
+
+#include <cstdint>
+
+
+uint BytesFromFmt(FmtType type) noexcept
+{
+    switch(type)
+    {
+    case FmtUByte: return sizeof(uint8_t);
+    case FmtShort: return sizeof(int16_t);
+    case FmtFloat: return sizeof(float);
+    case FmtDouble: return sizeof(double);
+    case FmtMulaw: return sizeof(uint8_t);
+    case FmtAlaw: return sizeof(uint8_t);
+    }
+    return 0;
+}
+
+uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept
+{
+    switch(chans)
+    {
+    case FmtMono: return 1;
+    case FmtStereo: return 2;
+    case FmtRear: return 2;
+    case FmtQuad: return 4;
+    case FmtX51: return 6;
+    case FmtX61: return 7;
+    case FmtX71: return 8;
+    case FmtBFormat2D: return (ambiorder*2) + 1;
+    case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
+    }
+    return 0;
+}

+ 72 - 0
Engine/lib/openal-soft/Alc/buffer_storage.h

@@ -0,0 +1,72 @@
+#ifndef ALC_BUFFER_STORAGE_H
+#define ALC_BUFFER_STORAGE_H
+
+#include <atomic>
+
+#include "albyte.h"
+
+
+using uint = unsigned int;
+
+/* Storable formats */
+enum FmtType : unsigned char {
+    FmtUByte,
+    FmtShort,
+    FmtFloat,
+    FmtDouble,
+    FmtMulaw,
+    FmtAlaw,
+};
+enum FmtChannels : unsigned char {
+    FmtMono,
+    FmtStereo,
+    FmtRear,
+    FmtQuad,
+    FmtX51, /* (WFX order) */
+    FmtX61, /* (WFX order) */
+    FmtX71, /* (WFX order) */
+    FmtBFormat2D,
+    FmtBFormat3D,
+};
+
+enum class AmbiLayout : unsigned char {
+    FuMa,
+    ACN,
+};
+enum class AmbiScaling : unsigned char {
+    FuMa,
+    SN3D,
+    N3D,
+};
+
+uint BytesFromFmt(FmtType type) noexcept;
+uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept;
+inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept
+{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); }
+
+
+using CallbackType = int(*)(void*, void*, int);
+
+struct BufferStorage {
+    CallbackType mCallback{nullptr};
+    void *mUserData{nullptr};
+
+    uint mSampleRate{0u};
+    FmtChannels mChannels{FmtMono};
+    FmtType mType{FmtShort};
+    uint mSampleLen{0u};
+
+    AmbiLayout mAmbiLayout{AmbiLayout::FuMa};
+    AmbiScaling mAmbiScaling{AmbiScaling::FuMa};
+    uint mAmbiOrder{0u};
+
+    inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); }
+    inline uint channelsFromFmt() const noexcept
+    { return ChannelsFromFmt(mChannels, mAmbiOrder); }
+    inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
+
+    inline bool isBFormat() const noexcept
+    { return mChannels == FmtBFormat2D || mChannels == FmtBFormat3D; }
+};
+
+#endif /* ALC_BUFFER_STORAGE_H */

+ 3 - 59
Engine/lib/openal-soft/Alc/compat.h

@@ -1,65 +1,9 @@
 #ifndef AL_COMPAT_H
 #define AL_COMPAT_H
 
-#include "alstring.h"
+#include <string>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifdef _WIN32
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-WCHAR *strdupW(const WCHAR *str);
-
-/* Opens a file with standard I/O. The filename is expected to be UTF-8. */
-FILE *al_fopen(const char *fname, const char *mode);
-
-#define HAVE_DYNLOAD 1
-
-#else
-
-#define al_fopen fopen
-
-#if defined(HAVE_DLFCN_H) && !defined(IN_IDE_PARSER)
-#define HAVE_DYNLOAD 1
-#endif
-
-#endif
-
-struct FileMapping {
-#ifdef _WIN32
-    HANDLE file;
-    HANDLE fmap;
-#else
-    int fd;
-#endif
-    void *ptr;
-    size_t len;
-};
-struct FileMapping MapFileToMem(const char *fname);
-void UnmapFileMem(const struct FileMapping *mapping);
-
-void GetProcBinary(al_string *path, al_string *fname);
-
-#ifdef HAVE_DYNLOAD
-void *LoadLib(const char *name);
-void CloseLib(void *handle);
-void *GetSymbol(void *handle, const char *name);
-#endif
-
-#ifdef __ANDROID__
-#define JCALL(obj, func)  ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
-#define JCALL0(obj, func)  ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
-
-/** Returns a JNIEnv*. */
-void *Android_GetJNIEnv(void);
-#endif
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+struct PathNamePair { std::string path, fname; };
+const PathNamePair &GetProcBinary(void);
 
 #endif /* AL_COMPAT_H */

+ 0 - 468
Engine/lib/openal-soft/Alc/converter.c

@@ -1,468 +0,0 @@
-
-#include "config.h"
-
-#include "converter.h"
-
-#include "fpu_modes.h"
-#include "mixer/defs.h"
-
-
-SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate)
-{
-    SampleConverter *converter;
-    ALsizei step;
-
-    if(numchans <= 0 || srcRate <= 0 || dstRate <= 0)
-        return NULL;
-
-    converter = al_calloc(16, FAM_SIZE(SampleConverter, Chan, numchans));
-    converter->mSrcType = srcType;
-    converter->mDstType = dstType;
-    converter->mNumChannels = numchans;
-    converter->mSrcTypeSize = BytesFromDevFmt(srcType);
-    converter->mDstTypeSize = BytesFromDevFmt(dstType);
-
-    converter->mSrcPrepCount = 0;
-    converter->mFracOffset = 0;
-
-    /* Have to set the mixer FPU mode since that's what the resampler code expects. */
-    START_MIXER_MODE();
-    step = (ALsizei)mind(((ALdouble)srcRate/dstRate*FRACTIONONE) + 0.5,
-                         MAX_PITCH * FRACTIONONE);
-    converter->mIncrement = maxi(step, 1);
-    if(converter->mIncrement == FRACTIONONE)
-        converter->mResample = Resample_copy_C;
-    else
-    {
-        /* TODO: Allow other resamplers. */
-        BsincPrepare(converter->mIncrement, &converter->mState.bsinc, &bsinc12);
-        converter->mResample = SelectResampler(BSinc12Resampler);
-    }
-    END_MIXER_MODE();
-
-    return converter;
-}
-
-void DestroySampleConverter(SampleConverter **converter)
-{
-    if(converter)
-    {
-        al_free(*converter);
-        *converter = NULL;
-    }
-}
-
-
-static inline ALfloat Sample_ALbyte(ALbyte val)
-{ return val * (1.0f/128.0f); }
-static inline ALfloat Sample_ALubyte(ALubyte val)
-{ return Sample_ALbyte((ALint)val - 128); }
-
-static inline ALfloat Sample_ALshort(ALshort val)
-{ return val * (1.0f/32768.0f); }
-static inline ALfloat Sample_ALushort(ALushort val)
-{ return Sample_ALshort((ALint)val - 32768); }
-
-static inline ALfloat Sample_ALint(ALint val)
-{ return (val>>7) * (1.0f/16777216.0f); }
-static inline ALfloat Sample_ALuint(ALuint val)
-{ return Sample_ALint(val - INT_MAX - 1); }
-
-static inline ALfloat Sample_ALfloat(ALfloat val)
-{ return val; }
-
-#define DECL_TEMPLATE(T)                                                      \
-static inline void Load_##T(ALfloat *restrict dst, const T *restrict src,     \
-                            ALint srcstep, ALsizei samples)                   \
-{                                                                             \
-    ALsizei i;                                                                \
-    for(i = 0;i < samples;i++)                                                \
-        dst[i] = Sample_##T(src[i*srcstep]);                                  \
-}
-
-DECL_TEMPLATE(ALbyte)
-DECL_TEMPLATE(ALubyte)
-DECL_TEMPLATE(ALshort)
-DECL_TEMPLATE(ALushort)
-DECL_TEMPLATE(ALint)
-DECL_TEMPLATE(ALuint)
-DECL_TEMPLATE(ALfloat)
-
-#undef DECL_TEMPLATE
-
-static void LoadSamples(ALfloat *dst, const ALvoid *src, ALint srcstep, enum DevFmtType srctype, ALsizei samples)
-{
-    switch(srctype)
-    {
-        case DevFmtByte:
-            Load_ALbyte(dst, src, srcstep, samples);
-            break;
-        case DevFmtUByte:
-            Load_ALubyte(dst, src, srcstep, samples);
-            break;
-        case DevFmtShort:
-            Load_ALshort(dst, src, srcstep, samples);
-            break;
-        case DevFmtUShort:
-            Load_ALushort(dst, src, srcstep, samples);
-            break;
-        case DevFmtInt:
-            Load_ALint(dst, src, srcstep, samples);
-            break;
-        case DevFmtUInt:
-            Load_ALuint(dst, src, srcstep, samples);
-            break;
-        case DevFmtFloat:
-            Load_ALfloat(dst, src, srcstep, samples);
-            break;
-    }
-}
-
-
-static inline ALbyte ALbyte_Sample(ALfloat val)
-{ return fastf2i(clampf(val*128.0f, -128.0f, 127.0f)); }
-static inline ALubyte ALubyte_Sample(ALfloat val)
-{ return ALbyte_Sample(val)+128; }
-
-static inline ALshort ALshort_Sample(ALfloat val)
-{ return fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f)); }
-static inline ALushort ALushort_Sample(ALfloat val)
-{ return ALshort_Sample(val)+32768; }
-
-static inline ALint ALint_Sample(ALfloat val)
-{ return fastf2i(clampf(val*16777216.0f, -16777216.0f, 16777215.0f)) << 7; }
-static inline ALuint ALuint_Sample(ALfloat val)
-{ return ALint_Sample(val)+INT_MAX+1; }
-
-static inline ALfloat ALfloat_Sample(ALfloat val)
-{ return val; }
-
-#define DECL_TEMPLATE(T)                                                      \
-static inline void Store_##T(T *restrict dst, const ALfloat *restrict src,    \
-                             ALint dststep, ALsizei samples)                  \
-{                                                                             \
-    ALsizei i;                                                                \
-    for(i = 0;i < samples;i++)                                                \
-        dst[i*dststep] = T##_Sample(src[i]);                                  \
-}
-
-DECL_TEMPLATE(ALbyte)
-DECL_TEMPLATE(ALubyte)
-DECL_TEMPLATE(ALshort)
-DECL_TEMPLATE(ALushort)
-DECL_TEMPLATE(ALint)
-DECL_TEMPLATE(ALuint)
-DECL_TEMPLATE(ALfloat)
-
-#undef DECL_TEMPLATE
-
-static void StoreSamples(ALvoid *dst, const ALfloat *src, ALint dststep, enum DevFmtType dsttype, ALsizei samples)
-{
-    switch(dsttype)
-    {
-        case DevFmtByte:
-            Store_ALbyte(dst, src, dststep, samples);
-            break;
-        case DevFmtUByte:
-            Store_ALubyte(dst, src, dststep, samples);
-            break;
-        case DevFmtShort:
-            Store_ALshort(dst, src, dststep, samples);
-            break;
-        case DevFmtUShort:
-            Store_ALushort(dst, src, dststep, samples);
-            break;
-        case DevFmtInt:
-            Store_ALint(dst, src, dststep, samples);
-            break;
-        case DevFmtUInt:
-            Store_ALuint(dst, src, dststep, samples);
-            break;
-        case DevFmtFloat:
-            Store_ALfloat(dst, src, dststep, samples);
-            break;
-    }
-}
-
-
-ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes)
-{
-    ALint prepcount = converter->mSrcPrepCount;
-    ALsizei increment = converter->mIncrement;
-    ALsizei DataPosFrac = converter->mFracOffset;
-    ALuint64 DataSize64;
-
-    if(prepcount < 0)
-    {
-        /* Negative prepcount means we need to skip that many input samples. */
-        if(-prepcount >= srcframes)
-            return 0;
-        srcframes += prepcount;
-        prepcount = 0;
-    }
-
-    if(srcframes < 1)
-    {
-        /* No output samples if there's no input samples. */
-        return 0;
-    }
-
-    if(prepcount < MAX_RESAMPLE_PADDING*2 &&
-       MAX_RESAMPLE_PADDING*2 - prepcount >= srcframes)
-    {
-        /* Not enough input samples to generate an output sample. */
-        return 0;
-    }
-
-    DataSize64  = prepcount;
-    DataSize64 += srcframes;
-    DataSize64 -= MAX_RESAMPLE_PADDING*2;
-    DataSize64 <<= FRACTIONBITS;
-    DataSize64 -= DataPosFrac;
-
-    /* If we have a full prep, we can generate at least one sample. */
-    return (ALsizei)clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE);
-}
-
-
-ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes)
-{
-    const ALsizei SrcFrameSize = converter->mNumChannels * converter->mSrcTypeSize;
-    const ALsizei DstFrameSize = converter->mNumChannels * converter->mDstTypeSize;
-    const ALsizei increment = converter->mIncrement;
-    ALsizei pos = 0;
-
-    START_MIXER_MODE();
-    while(pos < dstframes && *srcframes > 0)
-    {
-        ALfloat *restrict SrcData = ASSUME_ALIGNED(converter->mSrcSamples, 16);
-        ALfloat *restrict DstData = ASSUME_ALIGNED(converter->mDstSamples, 16);
-        ALint prepcount = converter->mSrcPrepCount;
-        ALsizei DataPosFrac = converter->mFracOffset;
-        ALuint64 DataSize64;
-        ALsizei DstSize;
-        ALint toread;
-        ALsizei chan;
-
-        if(prepcount < 0)
-        {
-            /* Negative prepcount means we need to skip that many input samples. */
-            if(-prepcount >= *srcframes)
-            {
-                converter->mSrcPrepCount = prepcount + *srcframes;
-                *srcframes = 0;
-                break;
-            }
-            *src = (const ALbyte*)*src + SrcFrameSize*-prepcount;
-            *srcframes += prepcount;
-            converter->mSrcPrepCount = 0;
-            continue;
-        }
-        toread = mini(*srcframes, BUFFERSIZE - MAX_RESAMPLE_PADDING*2);
-
-        if(prepcount < MAX_RESAMPLE_PADDING*2 &&
-           MAX_RESAMPLE_PADDING*2 - prepcount >= toread)
-        {
-            /* Not enough input samples to generate an output sample. Store
-             * what we're given for later.
-             */
-            for(chan = 0;chan < converter->mNumChannels;chan++)
-                LoadSamples(&converter->Chan[chan].mPrevSamples[prepcount],
-                    (const ALbyte*)*src + converter->mSrcTypeSize*chan,
-                    converter->mNumChannels, converter->mSrcType, toread
-                );
-
-            converter->mSrcPrepCount = prepcount + toread;
-            *srcframes = 0;
-            break;
-        }
-
-        DataSize64  = prepcount;
-        DataSize64 += toread;
-        DataSize64 -= MAX_RESAMPLE_PADDING*2;
-        DataSize64 <<= FRACTIONBITS;
-        DataSize64 -= DataPosFrac;
-
-        /* If we have a full prep, we can generate at least one sample. */
-        DstSize = (ALsizei)clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE);
-        DstSize = mini(DstSize, dstframes-pos);
-
-        for(chan = 0;chan < converter->mNumChannels;chan++)
-        {
-            const ALbyte *SrcSamples = (const ALbyte*)*src + converter->mSrcTypeSize*chan;
-            ALbyte *DstSamples = (ALbyte*)dst + converter->mDstTypeSize*chan;
-            const ALfloat *ResampledData;
-            ALsizei SrcDataEnd;
-
-            /* Load the previous samples into the source data first, then the
-             * new samples from the input buffer.
-             */
-            memcpy(SrcData, converter->Chan[chan].mPrevSamples,
-                   prepcount*sizeof(ALfloat));
-            LoadSamples(SrcData + prepcount, SrcSamples,
-                converter->mNumChannels, converter->mSrcType, toread
-            );
-
-            /* Store as many prep samples for next time as possible, given the
-             * number of output samples being generated.
-             */
-            SrcDataEnd = (DataPosFrac + increment*DstSize)>>FRACTIONBITS;
-            if(SrcDataEnd >= prepcount+toread)
-                memset(converter->Chan[chan].mPrevSamples, 0,
-                       sizeof(converter->Chan[chan].mPrevSamples));
-            else
-            {
-                size_t len = mini(MAX_RESAMPLE_PADDING*2, prepcount+toread-SrcDataEnd);
-                memcpy(converter->Chan[chan].mPrevSamples, &SrcData[SrcDataEnd],
-                       len*sizeof(ALfloat));
-                memset(converter->Chan[chan].mPrevSamples+len, 0,
-                       sizeof(converter->Chan[chan].mPrevSamples) - len*sizeof(ALfloat));
-            }
-
-            /* Now resample, and store the result in the output buffer. */
-            ResampledData = converter->mResample(&converter->mState,
-                SrcData+MAX_RESAMPLE_PADDING, DataPosFrac, increment,
-                DstData, DstSize
-            );
-
-            StoreSamples(DstSamples, ResampledData, converter->mNumChannels,
-                         converter->mDstType, DstSize);
-        }
-
-        /* Update the number of prep samples still available, as well as the
-         * fractional offset.
-         */
-        DataPosFrac += increment*DstSize;
-        converter->mSrcPrepCount = mini(prepcount + toread - (DataPosFrac>>FRACTIONBITS),
-                                        MAX_RESAMPLE_PADDING*2);
-        converter->mFracOffset = DataPosFrac & FRACTIONMASK;
-
-        /* Update the src and dst pointers in case there's still more to do. */
-        *src = (const ALbyte*)*src + SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
-        *srcframes -= mini(*srcframes, (DataPosFrac>>FRACTIONBITS));
-
-        dst = (ALbyte*)dst + DstFrameSize*DstSize;
-        pos += DstSize;
-    }
-    END_MIXER_MODE();
-
-    return pos;
-}
-
-
-ChannelConverter *CreateChannelConverter(enum DevFmtType srcType, enum DevFmtChannels srcChans, enum DevFmtChannels dstChans)
-{
-    ChannelConverter *converter;
-
-    if(srcChans != dstChans && !((srcChans == DevFmtMono && dstChans == DevFmtStereo) ||
-                                 (srcChans == DevFmtStereo && dstChans == DevFmtMono)))
-        return NULL;
-
-    converter = al_calloc(DEF_ALIGN, sizeof(*converter));
-    converter->mSrcType = srcType;
-    converter->mSrcChans = srcChans;
-    converter->mDstChans = dstChans;
-
-    return converter;
-}
-
-void DestroyChannelConverter(ChannelConverter **converter)
-{
-    if(converter)
-    {
-        al_free(*converter);
-        *converter = NULL;
-    }
-}
-
-
-#define DECL_TEMPLATE(T)                                                       \
-static void Mono2Stereo##T(ALfloat *restrict dst, const T *src, ALsizei frames)\
-{                                                                              \
-    ALsizei i;                                                                 \
-    for(i = 0;i < frames;i++)                                                  \
-        dst[i*2 + 1] = dst[i*2 + 0] = Sample_##T(src[i]) * 0.707106781187f;    \
-}                                                                              \
-                                                                               \
-static void Stereo2Mono##T(ALfloat *restrict dst, const T *src, ALsizei frames)\
-{                                                                              \
-    ALsizei i;                                                                 \
-    for(i = 0;i < frames;i++)                                                  \
-        dst[i] = (Sample_##T(src[i*2 + 0])+Sample_##T(src[i*2 + 1])) *         \
-                 0.707106781187f;                                              \
-}
-
-DECL_TEMPLATE(ALbyte)
-DECL_TEMPLATE(ALubyte)
-DECL_TEMPLATE(ALshort)
-DECL_TEMPLATE(ALushort)
-DECL_TEMPLATE(ALint)
-DECL_TEMPLATE(ALuint)
-DECL_TEMPLATE(ALfloat)
-
-#undef DECL_TEMPLATE
-
-void ChannelConverterInput(ChannelConverter *converter, const ALvoid *src, ALfloat *dst, ALsizei frames)
-{
-    if(converter->mSrcChans == converter->mDstChans)
-    {
-        LoadSamples(dst, src, 1, converter->mSrcType,
-                    frames*ChannelsFromDevFmt(converter->mSrcChans, 0));
-        return;
-    }
-
-    if(converter->mSrcChans == DevFmtStereo && converter->mDstChans == DevFmtMono)
-    {
-        switch(converter->mSrcType)
-        {
-            case DevFmtByte:
-                Stereo2MonoALbyte(dst, src, frames);
-                break;
-            case DevFmtUByte:
-                Stereo2MonoALubyte(dst, src, frames);
-                break;
-            case DevFmtShort:
-                Stereo2MonoALshort(dst, src, frames);
-                break;
-            case DevFmtUShort:
-                Stereo2MonoALushort(dst, src, frames);
-                break;
-            case DevFmtInt:
-                Stereo2MonoALint(dst, src, frames);
-                break;
-            case DevFmtUInt:
-                Stereo2MonoALuint(dst, src, frames);
-                break;
-            case DevFmtFloat:
-                Stereo2MonoALfloat(dst, src, frames);
-                break;
-        }
-    }
-    else /*if(converter->mSrcChans == DevFmtMono && converter->mDstChans == DevFmtStereo)*/
-    {
-        switch(converter->mSrcType)
-        {
-            case DevFmtByte:
-                Mono2StereoALbyte(dst, src, frames);
-                break;
-            case DevFmtUByte:
-                Mono2StereoALubyte(dst, src, frames);
-                break;
-            case DevFmtShort:
-                Mono2StereoALshort(dst, src, frames);
-                break;
-            case DevFmtUShort:
-                Mono2StereoALushort(dst, src, frames);
-                break;
-            case DevFmtInt:
-                Mono2StereoALint(dst, src, frames);
-                break;
-            case DevFmtUInt:
-                Mono2StereoALuint(dst, src, frames);
-                break;
-            case DevFmtFloat:
-                Mono2StereoALfloat(dst, src, frames);
-                break;
-        }
-    }
-}

+ 371 - 0
Engine/lib/openal-soft/Alc/converter.cpp

@@ -0,0 +1,371 @@
+
+#include "config.h"
+
+#include "converter.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <iterator>
+#include <limits.h>
+
+#include "albit.h"
+#include "albyte.h"
+#include "alnumeric.h"
+#include "core/fpu_ctrl.h"
+
+struct CTag;
+struct CopyTag;
+
+
+namespace {
+
+constexpr uint MaxPitch{10};
+
+static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
+static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
+    "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
+
+/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
+ * chokes on that given the inline specializations.
+ */
+template<DevFmtType T>
+inline float LoadSample(DevFmtType_t<T> val) noexcept;
+
+template<> inline float LoadSample<DevFmtByte>(DevFmtType_t<DevFmtByte> val) noexcept
+{ return val * (1.0f/128.0f); }
+template<> inline float LoadSample<DevFmtShort>(DevFmtType_t<DevFmtShort> val) noexcept
+{ return val * (1.0f/32768.0f); }
+template<> inline float LoadSample<DevFmtInt>(DevFmtType_t<DevFmtInt> val) noexcept
+{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
+template<> inline float LoadSample<DevFmtFloat>(DevFmtType_t<DevFmtFloat> val) noexcept
+{ return val; }
+
+template<> inline float LoadSample<DevFmtUByte>(DevFmtType_t<DevFmtUByte> val) noexcept
+{ return LoadSample<DevFmtByte>(static_cast<int8_t>(val - 128)); }
+template<> inline float LoadSample<DevFmtUShort>(DevFmtType_t<DevFmtUShort> val) noexcept
+{ return LoadSample<DevFmtShort>(static_cast<int16_t>(val - 32768)); }
+template<> inline float LoadSample<DevFmtUInt>(DevFmtType_t<DevFmtUInt> val) noexcept
+{ return LoadSample<DevFmtInt>(static_cast<int32_t>(val - 2147483648u)); }
+
+
+template<DevFmtType T>
+inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep,
+    const size_t samples) noexcept
+{
+    const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
+    for(size_t i{0u};i < samples;i++)
+        dst[i] = LoadSample<T>(ssrc[i*srcstep]);
+}
+
+void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype,
+    const size_t samples) noexcept
+{
+#define HANDLE_FMT(T)                                                         \
+    case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
+    switch(srctype)
+    {
+        HANDLE_FMT(DevFmtByte);
+        HANDLE_FMT(DevFmtUByte);
+        HANDLE_FMT(DevFmtShort);
+        HANDLE_FMT(DevFmtUShort);
+        HANDLE_FMT(DevFmtInt);
+        HANDLE_FMT(DevFmtUInt);
+        HANDLE_FMT(DevFmtFloat);
+    }
+#undef HANDLE_FMT
+}
+
+
+template<DevFmtType T>
+inline DevFmtType_t<T> StoreSample(float) noexcept;
+
+template<> inline float StoreSample<DevFmtFloat>(float val) noexcept
+{ return val; }
+template<> inline int32_t StoreSample<DevFmtInt>(float val) noexcept
+{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
+template<> inline int16_t StoreSample<DevFmtShort>(float val) noexcept
+{ return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
+template<> inline int8_t StoreSample<DevFmtByte>(float val) noexcept
+{ return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
+
+/* Define unsigned output variations. */
+template<> inline uint32_t StoreSample<DevFmtUInt>(float val) noexcept
+{ return static_cast<uint32_t>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
+template<> inline uint16_t StoreSample<DevFmtUShort>(float val) noexcept
+{ return static_cast<uint16_t>(StoreSample<DevFmtShort>(val) + 32768); }
+template<> inline uint8_t StoreSample<DevFmtUByte>(float val) noexcept
+{ return static_cast<uint8_t>(StoreSample<DevFmtByte>(val) + 128); }
+
+template<DevFmtType T>
+inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep,
+    const size_t samples) noexcept
+{
+    DevFmtType_t<T> *sdst = static_cast<DevFmtType_t<T>*>(dst);
+    for(size_t i{0u};i < samples;i++)
+        sdst[i*dststep] = StoreSample<T>(src[i]);
+}
+
+
+void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype,
+    const size_t samples) noexcept
+{
+#define HANDLE_FMT(T)                                                         \
+    case T: StoreSampleArray<T>(dst, src, dststep, samples); break
+    switch(dsttype)
+    {
+        HANDLE_FMT(DevFmtByte);
+        HANDLE_FMT(DevFmtUByte);
+        HANDLE_FMT(DevFmtShort);
+        HANDLE_FMT(DevFmtUShort);
+        HANDLE_FMT(DevFmtInt);
+        HANDLE_FMT(DevFmtUInt);
+        HANDLE_FMT(DevFmtFloat);
+    }
+#undef HANDLE_FMT
+}
+
+
+template<DevFmtType T>
+void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept
+{
+    const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
+    for(size_t i{0u};i < frames;i++)
+        dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
+}
+
+template<DevFmtType T>
+void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst,
+    const void *src, const size_t frames) noexcept
+{
+    const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
+    std::fill_n(dst, frames, 0.0f);
+    for(size_t c{0};chanmask;++c)
+    {
+        if LIKELY((chanmask&1))
+        {
+            for(size_t i{0u};i < frames;i++)
+                dst[i] += LoadSample<T>(ssrc[i*step + c]);
+        }
+        chanmask >>= 1;
+    }
+    for(size_t i{0u};i < frames;i++)
+        dst[i] *= scale;
+}
+
+} // namespace
+
+SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
+    uint srcRate, uint dstRate, Resampler resampler)
+{
+    if(numchans < 1 || srcRate < 1 || dstRate < 1)
+        return nullptr;
+
+    SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}};
+    converter->mSrcType = srcType;
+    converter->mDstType = dstType;
+    converter->mSrcTypeSize = BytesFromDevFmt(srcType);
+    converter->mDstTypeSize = BytesFromDevFmt(dstType);
+
+    converter->mSrcPrepCount = 0;
+    converter->mFracOffset = 0;
+
+    /* Have to set the mixer FPU mode since that's what the resampler code expects. */
+    FPUCtl mixer_mode{};
+    auto step = static_cast<uint>(
+        mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne));
+    converter->mIncrement = maxu(step, 1);
+    if(converter->mIncrement == MixerFracOne)
+        converter->mResample = Resample_<CopyTag,CTag>;
+    else
+        converter->mResample = PrepareResampler(resampler, converter->mIncrement,
+            &converter->mState);
+
+    return converter;
+}
+
+uint SampleConverter::availableOut(uint srcframes) const
+{
+    int prepcount{mSrcPrepCount};
+    if(prepcount < 0)
+    {
+        /* Negative prepcount means we need to skip that many input samples. */
+        if(static_cast<uint>(-prepcount) >= srcframes)
+            return 0;
+        srcframes -= static_cast<uint>(-prepcount);
+        prepcount = 0;
+    }
+
+    if(srcframes < 1)
+    {
+        /* No output samples if there's no input samples. */
+        return 0;
+    }
+
+    if(prepcount < MaxResamplerPadding
+        && static_cast<uint>(MaxResamplerPadding - prepcount) >= srcframes)
+    {
+        /* Not enough input samples to generate an output sample. */
+        return 0;
+    }
+
+    auto DataSize64 = static_cast<uint64_t>(prepcount);
+    DataSize64 += srcframes;
+    DataSize64 -= MaxResamplerPadding;
+    DataSize64 <<= MixerFracBits;
+    DataSize64 -= mFracOffset;
+
+    /* If we have a full prep, we can generate at least one sample. */
+    return static_cast<uint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1,
+        std::numeric_limits<int>::max()));
+}
+
+uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes)
+{
+    const uint SrcFrameSize{static_cast<uint>(mChan.size()) * mSrcTypeSize};
+    const uint DstFrameSize{static_cast<uint>(mChan.size()) * mDstTypeSize};
+    const uint increment{mIncrement};
+    auto SamplesIn = static_cast<const al::byte*>(*src);
+    uint NumSrcSamples{*srcframes};
+
+    FPUCtl mixer_mode{};
+    uint pos{0};
+    while(pos < dstframes && NumSrcSamples > 0)
+    {
+        int prepcount{mSrcPrepCount};
+        if(prepcount < 0)
+        {
+            /* Negative prepcount means we need to skip that many input samples. */
+            if(static_cast<uint>(-prepcount) >= NumSrcSamples)
+            {
+                mSrcPrepCount = static_cast<int>(NumSrcSamples) + prepcount;
+                NumSrcSamples = 0;
+                break;
+            }
+            SamplesIn += SrcFrameSize*static_cast<uint>(-prepcount);
+            NumSrcSamples -= static_cast<uint>(-prepcount);
+            mSrcPrepCount = 0;
+            continue;
+        }
+        const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)};
+
+        if(prepcount < MaxResamplerPadding
+            && static_cast<uint>(MaxResamplerPadding - prepcount) >= toread)
+        {
+            /* Not enough input samples to generate an output sample. Store
+             * what we're given for later.
+             */
+            for(size_t chan{0u};chan < mChan.size();chan++)
+                LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
+                    mChan.size(), mSrcType, toread);
+
+            mSrcPrepCount = prepcount + static_cast<int>(toread);
+            NumSrcSamples = 0;
+            break;
+        }
+
+        float *RESTRICT SrcData{mSrcSamples};
+        float *RESTRICT DstData{mDstSamples};
+        uint DataPosFrac{mFracOffset};
+        auto DataSize64 = static_cast<uint64_t>(prepcount);
+        DataSize64 += toread;
+        DataSize64 -= MaxResamplerPadding;
+        DataSize64 <<= MixerFracBits;
+        DataSize64 -= DataPosFrac;
+
+        /* If we have a full prep, we can generate at least one sample. */
+        auto DstSize = static_cast<uint>(
+            clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize));
+        DstSize = minu(DstSize, dstframes-pos);
+
+        for(size_t chan{0u};chan < mChan.size();chan++)
+        {
+            const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
+            al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan;
+
+            /* Load the previous samples into the source data first, then the
+             * new samples from the input buffer.
+             */
+            std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
+            LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
+
+            /* Store as many prep samples for next time as possible, given the
+             * number of output samples being generated.
+             */
+            uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits};
+            if(SrcDataEnd >= static_cast<uint>(prepcount)+toread)
+                std::fill(std::begin(mChan[chan].PrevSamples),
+                    std::end(mChan[chan].PrevSamples), 0.0f);
+            else
+            {
+                const size_t len{minz(al::size(mChan[chan].PrevSamples),
+                    static_cast<uint>(prepcount)+toread-SrcDataEnd)};
+                std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
+                std::fill(std::begin(mChan[chan].PrevSamples)+len,
+                    std::end(mChan[chan].PrevSamples), 0.0f);
+            }
+
+            /* Now resample, and store the result in the output buffer. */
+            const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1),
+                DataPosFrac, increment, {DstData, DstSize})};
+
+            StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
+        }
+
+        /* Update the number of prep samples still available, as well as the
+         * fractional offset.
+         */
+        DataPosFrac += increment*DstSize;
+        mSrcPrepCount = mini(prepcount + static_cast<int>(toread - (DataPosFrac>>MixerFracBits)),
+            MaxResamplerPadding);
+        mFracOffset = DataPosFrac & MixerFracMask;
+
+        /* Update the src and dst pointers in case there's still more to do. */
+        SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits);
+        NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits));
+
+        dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
+        pos += DstSize;
+    }
+
+    *src = SamplesIn;
+    *srcframes = NumSrcSamples;
+
+    return pos;
+}
+
+
+void ChannelConverter::convert(const void *src, float *dst, uint frames) const
+{
+    if(mDstChans == DevFmtMono)
+    {
+        const float scale{std::sqrt(1.0f / static_cast<float>(al::popcount(mChanMask)))};
+        switch(mSrcType)
+        {
+#define HANDLE_FMT(T) case T: Multi2Mono<T>(mChanMask, mSrcStep, scale, dst, src, frames); break
+        HANDLE_FMT(DevFmtByte);
+        HANDLE_FMT(DevFmtUByte);
+        HANDLE_FMT(DevFmtShort);
+        HANDLE_FMT(DevFmtUShort);
+        HANDLE_FMT(DevFmtInt);
+        HANDLE_FMT(DevFmtUInt);
+        HANDLE_FMT(DevFmtFloat);
+#undef HANDLE_FMT
+        }
+    }
+    else if(mChanMask == 0x1 && mDstChans == DevFmtStereo)
+    {
+        switch(mSrcType)
+        {
+#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
+        HANDLE_FMT(DevFmtByte);
+        HANDLE_FMT(DevFmtUByte);
+        HANDLE_FMT(DevFmtShort);
+        HANDLE_FMT(DevFmtUShort);
+        HANDLE_FMT(DevFmtInt);
+        HANDLE_FMT(DevFmtUInt);
+        HANDLE_FMT(DevFmtFloat);
+#undef HANDLE_FMT
+        }
+    }
+}

+ 41 - 37
Engine/lib/openal-soft/Alc/converter.h

@@ -1,55 +1,59 @@
 #ifndef CONVERTER_H
 #define CONVERTER_H
 
-#include "alMain.h"
-#include "alu.h"
+#include <cstddef>
+#include <memory>
 
-#ifdef __cpluspluc
-extern "C" {
-#endif
+#include "almalloc.h"
+#include "core/devformat.h"
+#include "core/mixer/defs.h"
 
-typedef struct SampleConverter {
-    enum DevFmtType mSrcType;
-    enum DevFmtType mDstType;
-    ALsizei mNumChannels;
-    ALsizei mSrcTypeSize;
-    ALsizei mDstTypeSize;
+using uint = unsigned int;
 
-    ALint mSrcPrepCount;
 
-    ALsizei mFracOffset;
-    ALsizei mIncrement;
-    InterpState mState;
-    ResamplerFunc mResample;
+struct SampleConverter {
+    DevFmtType mSrcType{};
+    DevFmtType mDstType{};
+    uint mSrcTypeSize{};
+    uint mDstTypeSize{};
 
-    alignas(16) ALfloat mSrcSamples[BUFFERSIZE];
-    alignas(16) ALfloat mDstSamples[BUFFERSIZE];
+    int mSrcPrepCount{};
 
-    struct {
-        alignas(16) ALfloat mPrevSamples[MAX_RESAMPLE_PADDING*2];
-    } Chan[];
-} SampleConverter;
+    uint mFracOffset{};
+    uint mIncrement{};
+    InterpState mState{};
+    ResamplerFunc mResample{};
 
-SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate);
-void DestroySampleConverter(SampleConverter **converter);
+    alignas(16) float mSrcSamples[BufferLineSize]{};
+    alignas(16) float mDstSamples[BufferLineSize]{};
 
-ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes);
-ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes);
+    struct ChanSamples {
+        alignas(16) float PrevSamples[MaxResamplerPadding];
+    };
+    al::FlexArray<ChanSamples> mChan;
 
+    SampleConverter(size_t numchans) : mChan{numchans} { }
 
-typedef struct ChannelConverter {
-    enum DevFmtType mSrcType;
-    enum DevFmtChannels mSrcChans;
-    enum DevFmtChannels mDstChans;
-} ChannelConverter;
+    uint convert(const void **src, uint *srcframes, void *dst, uint dstframes);
+    uint availableOut(uint srcframes) const;
 
-ChannelConverter *CreateChannelConverter(enum DevFmtType srcType, enum DevFmtChannels srcChans, enum DevFmtChannels dstChans);
-void DestroyChannelConverter(ChannelConverter **converter);
+    DEF_FAM_NEWDEL(SampleConverter, mChan)
+};
+using SampleConverterPtr = std::unique_ptr<SampleConverter>;
 
-void ChannelConverterInput(ChannelConverter *converter, const ALvoid *src, ALfloat *dst, ALsizei frames);
+SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
+    uint srcRate, uint dstRate, Resampler resampler);
 
-#ifdef __cpluspluc
-}
-#endif
+
+struct ChannelConverter {
+    DevFmtType mSrcType{};
+    uint mSrcStep{};
+    uint mChanMask{};
+    DevFmtChannels mDstChans{};
+
+    bool is_active() const noexcept { return mChanMask != 0; }
+
+    void convert(const void *src, float *dst, uint frames) const;
+};
 
 #endif /* CONVERTER_H */

+ 0 - 15
Engine/lib/openal-soft/Alc/cpu_caps.h

@@ -1,15 +0,0 @@
-#ifndef CPU_CAPS_H
-#define CPU_CAPS_H
-
-extern int CPUCapFlags;
-enum {
-    CPU_CAP_SSE    = 1<<0,
-    CPU_CAP_SSE2   = 1<<1,
-    CPU_CAP_SSE3   = 1<<2,
-    CPU_CAP_SSE4_1 = 1<<3,
-    CPU_CAP_NEON   = 1<<4,
-};
-
-void FillCPUCaps(int capfilter);
-
-#endif /* CPU_CAPS_H */

+ 212 - 0
Engine/lib/openal-soft/Alc/effects/autowah.cpp

@@ -0,0 +1,212 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by Raul Herraiz.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "core/filters/biquad.h"
+#include "effectslot.h"
+#include "vecmat.h"
+
+namespace {
+
+constexpr float GainScale{31621.0f};
+constexpr float MinFreq{20.0f};
+constexpr float MaxFreq{2500.0f};
+constexpr float QFactor{5.0f};
+
+struct AutowahState final : public EffectState {
+    /* Effect parameters */
+    float mAttackRate;
+    float mReleaseRate;
+    float mResonanceGain;
+    float mPeakGain;
+    float mFreqMinNorm;
+    float mBandwidthNorm;
+    float mEnvDelay;
+
+    /* Filter components derived from the envelope. */
+    struct {
+        float cos_w0;
+        float alpha;
+    } mEnv[BufferLineSize];
+
+    struct {
+        /* Effect filters' history. */
+        struct {
+            float z1, z2;
+        } Filter;
+
+        /* Effect gains for each output channel */
+        float CurrentGains[MAX_OUTPUT_CHANNELS];
+        float TargetGains[MAX_OUTPUT_CHANNELS];
+    } mChans[MaxAmbiChannels];
+
+    /* Effects buffers */
+    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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(AutowahState)
+};
+
+void AutowahState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    /* (Re-)initializing parameters and clear the buffers. */
+
+    mAttackRate    = 1.0f;
+    mReleaseRate   = 1.0f;
+    mResonanceGain = 10.0f;
+    mPeakGain      = 4.5f;
+    mFreqMinNorm   = 4.5e-4f;
+    mBandwidthNorm = 0.05f;
+    mEnvDelay      = 0.0f;
+
+    for(auto &e : mEnv)
+    {
+        e.cos_w0 = 0.0f;
+        e.alpha = 0.0f;
+    }
+
+    for(auto &chan : mChans)
+    {
+        std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
+        chan.Filter.z1 = 0.0f;
+        chan.Filter.z2 = 0.0f;
+    }
+}
+
+void AutowahState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+    const auto frequency = static_cast<float>(device->Frequency);
+
+    const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
+
+    mAttackRate    = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
+    mReleaseRate   = std::exp(-1.0f / (ReleaseTime*frequency));
+    /* 0-20dB Resonance Peak gain */
+    mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
+    mPeakGain      = 1.0f - std::log10(props->Autowah.PeakGain / GainScale);
+    mFreqMinNorm   = MinFreq / frequency;
+    mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
+
+    mOutTarget = target.Main->Buffer;
+    auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
+    { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
+    SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
+}
+
+void AutowahState::process(const size_t samplesToDo,
+    const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    const float attack_rate{mAttackRate};
+    const float release_rate{mReleaseRate};
+    const float res_gain{mResonanceGain};
+    const float peak_gain{mPeakGain};
+    const float freq_min{mFreqMinNorm};
+    const float bandwidth{mBandwidthNorm};
+
+    float env_delay{mEnvDelay};
+    for(size_t i{0u};i < samplesToDo;i++)
+    {
+        float w0, sample, a;
+
+        /* Envelope follower described on the book: Audio Effects, Theory,
+         * Implementation and Application.
+         */
+        sample = peak_gain * std::fabs(samplesIn[0][i]);
+        a = (sample > env_delay) ? attack_rate : release_rate;
+        env_delay = lerp(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();
+        mEnv[i].cos_w0 = std::cos(w0);
+        mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
+    }
+    mEnvDelay = env_delay;
+
+    auto chandata = std::addressof(mChans[0]);
+    for(const auto &insamples : samplesIn)
+    {
+        /* This effectively inlines BiquadFilter_setParams for a peaking
+         * filter and BiquadFilter_processC. The alpha and cosine components
+         * for the filter coefficients were previously calculated with the
+         * envelope. Because the filter changes for each sample, the
+         * coefficients are transient and don't need to be held.
+         */
+        float z1{chandata->Filter.z1};
+        float z2{chandata->Filter.z2};
+
+        for(size_t i{0u};i < samplesToDo;i++)
+        {
+            const float alpha{mEnv[i].alpha};
+            const float cos_w0{mEnv[i].cos_w0};
+            float input, output;
+            float a[3], b[3];
+
+            b[0] =  1.0f + alpha*res_gain;
+            b[1] = -2.0f * cos_w0;
+            b[2] =  1.0f - alpha*res_gain;
+            a[0] =  1.0f + alpha/res_gain;
+            a[1] = -2.0f * cos_w0;
+            a[2] =  1.0f - alpha/res_gain;
+
+            input = insamples[i];
+            output = input*(b[0]/a[0]) + z1;
+            z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
+            z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
+            mBufferOut[i] = output;
+        }
+        chandata->Filter.z1 = z1;
+        chandata->Filter.z2 = z2;
+
+        /* Now, mix the processed sound data to the output. */
+        MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
+            chandata->TargetGains, samplesToDo, 0);
+        ++chandata;
+    }
+}
+
+
+struct AutowahStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new AutowahState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *AutowahStateFactory_getFactory()
+{
+    static AutowahStateFactory AutowahFactory{};
+    return &AutowahFactory;
+}

+ 212 - 0
Engine/lib/openal-soft/Alc/effects/base.h

@@ -0,0 +1,212 @@
+#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;
+};
+
+
+EffectStateFactory *NullStateFactory_getFactory(void);
+EffectStateFactory *ReverbStateFactory_getFactory(void);
+EffectStateFactory *StdReverbStateFactory_getFactory(void);
+EffectStateFactory *AutowahStateFactory_getFactory(void);
+EffectStateFactory *ChorusStateFactory_getFactory(void);
+EffectStateFactory *CompressorStateFactory_getFactory(void);
+EffectStateFactory *DistortionStateFactory_getFactory(void);
+EffectStateFactory *EchoStateFactory_getFactory(void);
+EffectStateFactory *EqualizerStateFactory_getFactory(void);
+EffectStateFactory *FlangerStateFactory_getFactory(void);
+EffectStateFactory *FshifterStateFactory_getFactory(void);
+EffectStateFactory *ModulatorStateFactory_getFactory(void);
+EffectStateFactory *PshifterStateFactory_getFactory(void);
+EffectStateFactory* VmorpherStateFactory_getFactory(void);
+
+EffectStateFactory *DedicatedStateFactory_getFactory(void);
+
+EffectStateFactory *ConvolutionStateFactory_getFactory(void);
+
+#endif /* EFFECTS_BASE_H */

+ 0 - 555
Engine/lib/openal-soft/Alc/effects/chorus.c

@@ -1,555 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2013 by Mike Gorchak
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-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");
-
-enum WaveForm {
-    WF_Sinusoid,
-    WF_Triangle
-};
-
-typedef struct ALchorusState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    ALfloat *SampleBuffer;
-    ALsizei BufferLength;
-    ALsizei offset;
-
-    ALsizei lfo_offset;
-    ALsizei lfo_range;
-    ALfloat lfo_scale;
-    ALint lfo_disp;
-
-    /* Gains for left and right sides */
-    struct {
-        ALfloat Current[MAX_OUTPUT_CHANNELS];
-        ALfloat Target[MAX_OUTPUT_CHANNELS];
-    } Gains[2];
-
-    /* effect parameters */
-    enum WaveForm waveform;
-    ALint delay;
-    ALfloat depth;
-    ALfloat feedback;
-} ALchorusState;
-
-static ALvoid ALchorusState_Destruct(ALchorusState *state);
-static ALboolean ALchorusState_deviceUpdate(ALchorusState *state, ALCdevice *Device);
-static ALvoid ALchorusState_update(ALchorusState *state, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props);
-static ALvoid ALchorusState_process(ALchorusState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALchorusState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALchorusState);
-
-
-static void ALchorusState_Construct(ALchorusState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALchorusState, ALeffectState, state);
-
-    state->BufferLength = 0;
-    state->SampleBuffer = NULL;
-    state->offset = 0;
-    state->lfo_offset = 0;
-    state->lfo_range = 1;
-    state->waveform = WF_Triangle;
-}
-
-static ALvoid ALchorusState_Destruct(ALchorusState *state)
-{
-    al_free(state->SampleBuffer);
-    state->SampleBuffer = NULL;
-
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALchorusState_deviceUpdate(ALchorusState *state, ALCdevice *Device)
-{
-    const ALfloat max_delay = maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY);
-    ALsizei maxlen;
-
-    maxlen = NextPowerOf2(float2int(max_delay*2.0f*Device->Frequency) + 1u);
-    if(maxlen <= 0) return AL_FALSE;
-
-    if(maxlen != state->BufferLength)
-    {
-        void *temp = al_calloc(16, maxlen * sizeof(ALfloat));
-        if(!temp) return AL_FALSE;
-
-        al_free(state->SampleBuffer);
-        state->SampleBuffer = temp;
-
-        state->BufferLength = maxlen;
-    }
-
-    memset(state->SampleBuffer, 0, state->BufferLength*sizeof(ALfloat));
-    memset(state->Gains, 0, sizeof(state->Gains));
-
-    return AL_TRUE;
-}
-
-static ALvoid ALchorusState_update(ALchorusState *state, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props)
-{
-    const ALsizei mindelay = MAX_RESAMPLE_PADDING << FRACTIONBITS;
-    const ALCdevice *device = Context->Device;
-    ALfloat frequency = (ALfloat)device->Frequency;
-    ALfloat coeffs[MAX_AMBI_COEFFS];
-    ALfloat rate;
-    ALint phase;
-
-    switch(props->Chorus.Waveform)
-    {
-        case AL_CHORUS_WAVEFORM_TRIANGLE:
-            state->waveform = WF_Triangle;
-            break;
-        case AL_CHORUS_WAVEFORM_SINUSOID:
-            state->waveform = WF_Sinusoid;
-            break;
-    }
-
-    /* The LFO depth is scaled to be relative to the sample delay. Clamp the
-     * delay and depth to allow enough padding for resampling.
-     */
-    state->delay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f),
-                        mindelay);
-    state->depth = minf(props->Chorus.Depth * state->delay,
-                        (ALfloat)(state->delay - mindelay));
-
-    state->feedback = props->Chorus.Feedback;
-
-    /* Gains for left and right sides */
-    CalcAngleCoeffs(-F_PI_2, 0.0f, 0.0f, coeffs);
-    ComputeDryPanGains(&device->Dry, coeffs, Slot->Params.Gain, state->Gains[0].Target);
-    CalcAngleCoeffs( F_PI_2, 0.0f, 0.0f, coeffs);
-    ComputeDryPanGains(&device->Dry, coeffs, Slot->Params.Gain, state->Gains[1].Target);
-
-    phase = props->Chorus.Phase;
-    rate = props->Chorus.Rate;
-    if(!(rate > 0.0f))
-    {
-        state->lfo_offset = 0;
-        state->lfo_range = 1;
-        state->lfo_scale = 0.0f;
-        state->lfo_disp = 0;
-    }
-    else
-    {
-        /* Calculate LFO coefficient (number of samples per cycle). Limit the
-         * max range to avoid overflow when calculating the displacement.
-         */
-        ALsizei lfo_range = float2int(minf(frequency/rate + 0.5f, (ALfloat)(INT_MAX/360 - 180)));
-
-        state->lfo_offset = float2int((ALfloat)state->lfo_offset/state->lfo_range*
-                                      lfo_range + 0.5f) % lfo_range;
-        state->lfo_range = lfo_range;
-        switch(state->waveform)
-        {
-            case WF_Triangle:
-                state->lfo_scale = 4.0f / state->lfo_range;
-                break;
-            case WF_Sinusoid:
-                state->lfo_scale = F_TAU / state->lfo_range;
-                break;
-        }
-
-        /* Calculate lfo phase displacement */
-        if(phase < 0) phase = 360 + phase;
-        state->lfo_disp = (state->lfo_range*phase + 180) / 360;
-    }
-}
-
-static void GetTriangleDelays(ALint *restrict delays, ALsizei offset, const ALsizei lfo_range,
-                              const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay,
-                              const ALsizei todo)
-{
-    ALsizei i;
-    for(i = 0;i < todo;i++)
-    {
-        delays[i] = fastf2i((1.0f - fabsf(2.0f - lfo_scale*offset)) * depth) + delay;
-        offset = (offset+1)%lfo_range;
-    }
-}
-
-static void GetSinusoidDelays(ALint *restrict delays, ALsizei offset, const ALsizei lfo_range,
-                              const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay,
-                              const ALsizei todo)
-{
-    ALsizei i;
-    for(i = 0;i < todo;i++)
-    {
-        delays[i] = fastf2i(sinf(lfo_scale*offset) * depth) + delay;
-        offset = (offset+1)%lfo_range;
-    }
-}
-
-
-static ALvoid ALchorusState_process(ALchorusState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    const ALsizei bufmask = state->BufferLength-1;
-    const ALfloat feedback = state->feedback;
-    const ALsizei avgdelay = (state->delay + (FRACTIONONE>>1)) >> FRACTIONBITS;
-    ALfloat *restrict delaybuf = state->SampleBuffer;
-    ALsizei offset = state->offset;
-    ALsizei i, c;
-    ALsizei base;
-
-    for(base = 0;base < SamplesToDo;)
-    {
-        const ALsizei todo = mini(256, SamplesToDo-base);
-        ALint moddelays[2][256];
-        alignas(16) ALfloat temps[2][256];
-
-        if(state->waveform == WF_Sinusoid)
-        {
-            GetSinusoidDelays(moddelays[0], state->lfo_offset, state->lfo_range, state->lfo_scale,
-                              state->depth, state->delay, todo);
-            GetSinusoidDelays(moddelays[1], (state->lfo_offset+state->lfo_disp)%state->lfo_range,
-                              state->lfo_range, state->lfo_scale, state->depth, state->delay,
-                              todo);
-        }
-        else /*if(state->waveform == WF_Triangle)*/
-        {
-            GetTriangleDelays(moddelays[0], state->lfo_offset, state->lfo_range, state->lfo_scale,
-                              state->depth, state->delay, todo);
-            GetTriangleDelays(moddelays[1], (state->lfo_offset+state->lfo_disp)%state->lfo_range,
-                              state->lfo_range, state->lfo_scale, state->depth, state->delay,
-                              todo);
-        }
-        state->lfo_offset = (state->lfo_offset+todo) % state->lfo_range;
-
-        for(i = 0;i < todo;i++)
-        {
-            ALint delay;
-            ALfloat mu;
-
-            // Feed the buffer's input first (necessary for delays < 1).
-            delaybuf[offset&bufmask] = SamplesIn[0][base+i];
-
-            // Tap for the left output.
-            delay = offset - (moddelays[0][i]>>FRACTIONBITS);
-            mu = (moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
-            temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay  ) & bufmask],
-                                delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
-                                mu);
-
-            // Tap for the right output.
-            delay = offset - (moddelays[1][i]>>FRACTIONBITS);
-            mu = (moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
-            temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay  ) & bufmask],
-                                delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask],
-                                mu);
-
-            // Accumulate feedback from the average delay of the taps.
-            delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
-            offset++;
-        }
-
-        for(c = 0;c < 2;c++)
-            MixSamples(temps[c], NumChannels, SamplesOut, state->Gains[c].Current,
-                       state->Gains[c].Target, SamplesToDo-base, base, todo);
-
-        base += todo;
-    }
-
-    state->offset = offset;
-}
-
-
-typedef struct ChorusStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} ChorusStateFactory;
-
-static ALeffectState *ChorusStateFactory_create(ChorusStateFactory *UNUSED(factory))
-{
-    ALchorusState *state;
-
-    NEW_OBJ0(state, ALchorusState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(ChorusStateFactory);
-
-
-EffectStateFactory *ChorusStateFactory_getFactory(void)
-{
-    static ChorusStateFactory ChorusFactory = { { GET_VTABLE2(ChorusStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &ChorusFactory);
-}
-
-
-void ALchorus_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_CHORUS_WAVEFORM:
-            if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
-            props->Chorus.Waveform = val;
-            break;
-
-        case AL_CHORUS_PHASE:
-            if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
-            props->Chorus.Phase = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
-    }
-}
-void ALchorus_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALchorus_setParami(effect, context, param, vals[0]); }
-void ALchorus_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_CHORUS_RATE:
-            if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
-            props->Chorus.Rate = val;
-            break;
-
-        case AL_CHORUS_DEPTH:
-            if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
-            props->Chorus.Depth = val;
-            break;
-
-        case AL_CHORUS_FEEDBACK:
-            if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
-            props->Chorus.Feedback = val;
-            break;
-
-        case AL_CHORUS_DELAY:
-            if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
-            props->Chorus.Delay = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
-    }
-}
-void ALchorus_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALchorus_setParamf(effect, context, param, vals[0]); }
-
-void ALchorus_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_CHORUS_WAVEFORM:
-            *val = props->Chorus.Waveform;
-            break;
-
-        case AL_CHORUS_PHASE:
-            *val = props->Chorus.Phase;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
-    }
-}
-void ALchorus_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALchorus_getParami(effect, context, param, vals); }
-void ALchorus_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_CHORUS_RATE:
-            *val = props->Chorus.Rate;
-            break;
-
-        case AL_CHORUS_DEPTH:
-            *val = props->Chorus.Depth;
-            break;
-
-        case AL_CHORUS_FEEDBACK:
-            *val = props->Chorus.Feedback;
-            break;
-
-        case AL_CHORUS_DELAY:
-            *val = props->Chorus.Delay;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
-    }
-}
-void ALchorus_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALchorus_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALchorus);
-
-
-/* Flanger is basically a chorus with a really short delay. They can both use
- * the same processing functions, so piggyback flanger on the chorus functions.
- */
-typedef struct FlangerStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} FlangerStateFactory;
-
-ALeffectState *FlangerStateFactory_create(FlangerStateFactory *UNUSED(factory))
-{
-    ALchorusState *state;
-
-    NEW_OBJ0(state, ALchorusState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(FlangerStateFactory);
-
-EffectStateFactory *FlangerStateFactory_getFactory(void)
-{
-    static FlangerStateFactory FlangerFactory = { { GET_VTABLE2(FlangerStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &FlangerFactory);
-}
-
-
-void ALflanger_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_FLANGER_WAVEFORM:
-            if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
-            props->Chorus.Waveform = val;
-            break;
-
-        case AL_FLANGER_PHASE:
-            if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
-            props->Chorus.Phase = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
-    }
-}
-void ALflanger_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALflanger_setParami(effect, context, param, vals[0]); }
-void ALflanger_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_FLANGER_RATE:
-            if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
-            props->Chorus.Rate = val;
-            break;
-
-        case AL_FLANGER_DEPTH:
-            if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
-            props->Chorus.Depth = val;
-            break;
-
-        case AL_FLANGER_FEEDBACK:
-            if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
-            props->Chorus.Feedback = val;
-            break;
-
-        case AL_FLANGER_DELAY:
-            if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
-            props->Chorus.Delay = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
-    }
-}
-void ALflanger_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALflanger_setParamf(effect, context, param, vals[0]); }
-
-void ALflanger_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_FLANGER_WAVEFORM:
-            *val = props->Chorus.Waveform;
-            break;
-
-        case AL_FLANGER_PHASE:
-            *val = props->Chorus.Phase;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
-    }
-}
-void ALflanger_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALflanger_getParami(effect, context, param, vals); }
-void ALflanger_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_FLANGER_RATE:
-            *val = props->Chorus.Rate;
-            break;
-
-        case AL_FLANGER_DEPTH:
-            *val = props->Chorus.Depth;
-            break;
-
-        case AL_FLANGER_FEEDBACK:
-            *val = props->Chorus.Feedback;
-            break;
-
-        case AL_FLANGER_DELAY:
-            *val = props->Chorus.Delay;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
-    }
-}
-void ALflanger_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALflanger_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALflanger);

+ 287 - 0
Engine/lib/openal-soft/Alc/effects/chorus.cpp

@@ -0,0 +1,287 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <algorithm>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <iterator>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.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 "opthelpers.h"
+#include "vector.h"
+
+
+namespace {
+
+#define MAX_UPDATE_SAMPLES 256
+
+struct ChorusState final : public EffectState {
+    al::vector<float,16> mSampleBuffer;
+    uint mOffset{0};
+
+    uint mLfoOffset{0};
+    uint mLfoRange{1};
+    float mLfoScale{0.0f};
+    uint mLfoDisp{0};
+
+    /* Gains for left and right sides */
+    struct {
+        float Current[MAX_OUTPUT_CHANNELS]{};
+        float Target[MAX_OUTPUT_CHANNELS]{};
+    } mGains[2];
+
+    /* effect parameters */
+    ChorusWaveform mWaveform{};
+    int mDelay{0};
+    float mDepth{0.0f};
+    float mFeedback{0.0f};
+
+    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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(ChorusState)
+};
+
+void ChorusState::deviceUpdate(const ALCdevice *Device, const Buffer&)
+{
+    constexpr float max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
+
+    const auto frequency = static_cast<float>(Device->Frequency);
+    const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
+    if(maxlen != mSampleBuffer.size())
+        al::vector<float,16>(maxlen).swap(mSampleBuffer);
+
+    std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
+    for(auto &e : mGains)
+    {
+        std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
+        std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
+    }
+}
+
+void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
+
+    /* 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 auto frequency = static_cast<float>(device->Frequency);
+
+    mWaveform = props->Chorus.Waveform;
+
+    mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
+    mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
+        static_cast<float>(mDelay - mindelay));
+
+    mFeedback = props->Chorus.Feedback;
+
+    /* Gains for left and right sides */
+    const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
+    const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
+
+    mOutTarget = target.Main->Buffer;
+    ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
+    ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
+
+    float rate{props->Chorus.Rate};
+    if(!(rate > 0.0f))
+    {
+        mLfoOffset = 0;
+        mLfoRange = 1;
+        mLfoScale = 0.0f;
+        mLfoDisp = 0;
+    }
+    else
+    {
+        /* Calculate LFO coefficient (number of samples per cycle). Limit the
+         * max range to avoid overflow when calculating the displacement.
+         */
+        uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))};
+
+        mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
+        mLfoRange = lfo_range;
+        switch(mWaveform)
+        {
+        case ChorusWaveform::Triangle:
+            mLfoScale = 4.0f / static_cast<float>(mLfoRange);
+            break;
+        case ChorusWaveform::Sinusoid:
+            mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
+            break;
+        }
+
+        /* Calculate lfo phase displacement */
+        int phase{props->Chorus.Phase};
+        if(phase < 0) phase = 360 + phase;
+        mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
+    }
+}
+
+
+void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
+{
+    const uint lfo_range{mLfoRange};
+    const float lfo_scale{mLfoScale};
+    const float depth{mDepth};
+    const int delay{mDelay};
+
+    ASSUME(lfo_range > 0);
+    ASSUME(todo > 0);
+
+    uint offset{mLfoOffset};
+    auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
+    {
+        offset = (offset+1)%lfo_range;
+        const float offset_norm{static_cast<float>(offset) * lfo_scale};
+        return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
+    };
+    std::generate_n(delays[0], todo, gen_lfo);
+
+    offset = (mLfoOffset+mLfoDisp) % lfo_range;
+    std::generate_n(delays[1], todo, gen_lfo);
+
+    mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
+}
+
+void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
+{
+    const uint lfo_range{mLfoRange};
+    const float lfo_scale{mLfoScale};
+    const float depth{mDepth};
+    const int delay{mDelay};
+
+    ASSUME(lfo_range > 0);
+    ASSUME(todo > 0);
+
+    uint offset{mLfoOffset};
+    auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
+    {
+        offset = (offset+1)%lfo_range;
+        const float offset_norm{static_cast<float>(offset) * lfo_scale};
+        return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
+    };
+    std::generate_n(delays[0], todo, gen_lfo);
+
+    offset = (mLfoOffset+mLfoDisp) % lfo_range;
+    std::generate_n(delays[1], todo, gen_lfo);
+
+    mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
+}
+
+void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    const size_t bufmask{mSampleBuffer.size()-1};
+    const float feedback{mFeedback};
+    const uint avgdelay{(static_cast<uint>(mDelay) + (MixerFracOne>>1)) >> MixerFracBits};
+    float *RESTRICT delaybuf{mSampleBuffer.data()};
+    uint offset{mOffset};
+
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
+
+        uint moddelays[2][MAX_UPDATE_SAMPLES];
+        if(mWaveform == ChorusWaveform::Sinusoid)
+            getSinusoidDelays(moddelays, todo);
+        else /*if(mWaveform == ChorusWaveform::Triangle)*/
+            getTriangleDelays(moddelays, todo);
+
+        alignas(16) float temps[2][MAX_UPDATE_SAMPLES];
+        for(size_t i{0u};i < todo;++i)
+        {
+            // Feed the buffer's input first (necessary for delays < 1).
+            delaybuf[offset&bufmask] = samplesIn[0][base+i];
+
+            // Tap for the left output.
+            uint delay{offset - (moddelays[0][i]>>MixerFracBits)};
+            float mu{static_cast<float>(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)};
+            temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay  ) & bufmask],
+                delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
+
+            // Tap for the right output.
+            delay = offset - (moddelays[1][i]>>MixerFracBits);
+            mu = static_cast<float>(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne);
+            temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay  ) & bufmask],
+                delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
+
+            // Accumulate feedback from the average delay of the taps.
+            delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
+            ++offset;
+        }
+
+        for(ALsizei c{0};c < 2;++c)
+            MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
+                samplesToDo-base, base);
+
+        base += todo;
+    }
+
+    mOffset = offset;
+}
+
+
+struct ChorusStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
+};
+
+
+/* Flanger is basically a chorus with a really short delay. They can both use
+ * the same processing functions, so piggyback flanger on the chorus functions.
+ */
+struct FlangerStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *ChorusStateFactory_getFactory()
+{
+    static ChorusStateFactory ChorusFactory{};
+    return &ChorusFactory;
+}
+
+EffectStateFactory *FlangerStateFactory_getFactory()
+{
+    static FlangerStateFactory FlangerFactory{};
+    return &FlangerFactory;
+}

+ 0 - 250
Engine/lib/openal-soft/Alc/effects/compressor.c

@@ -1,250 +0,0 @@
-/**
- * OpenAL 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.
- *
- * 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 <stdlib.h>
-
-#include "config.h"
-#include "alError.h"
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alu.h"
-
-
-typedef struct ALcompressorState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    /* Effect gains for each channel */
-    ALfloat Gain[MAX_EFFECT_CHANNELS][MAX_OUTPUT_CHANNELS];
-
-    /* Effect parameters */
-    ALboolean Enabled;
-    ALfloat AttackRate;
-    ALfloat ReleaseRate;
-    ALfloat GainCtrl;
-} ALcompressorState;
-
-static ALvoid ALcompressorState_Destruct(ALcompressorState *state);
-static ALboolean ALcompressorState_deviceUpdate(ALcompressorState *state, ALCdevice *device);
-static ALvoid ALcompressorState_update(ALcompressorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALcompressorState_process(ALcompressorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALcompressorState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALcompressorState);
-
-
-static void ALcompressorState_Construct(ALcompressorState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALcompressorState, ALeffectState, state);
-
-    state->Enabled = AL_TRUE;
-    state->AttackRate = 0.0f;
-    state->ReleaseRate = 0.0f;
-    state->GainCtrl = 1.0f;
-}
-
-static ALvoid ALcompressorState_Destruct(ALcompressorState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALcompressorState_deviceUpdate(ALcompressorState *state, ALCdevice *device)
-{
-    const ALfloat attackTime = device->Frequency * 0.2f; /* 200ms Attack */
-    const ALfloat releaseTime = device->Frequency * 0.4f; /* 400ms Release */
-
-    state->AttackRate = 1.0f / attackTime;
-    state->ReleaseRate = 1.0f / releaseTime;
-
-    return AL_TRUE;
-}
-
-static ALvoid ALcompressorState_update(ALcompressorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALuint i;
-
-    state->Enabled = props->Compressor.OnOff;
-
-    STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
-    STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
-    for(i = 0;i < 4;i++)
-        ComputeFirstOrderGains(&device->FOAOut, IdentityMatrixf.m[i],
-                               slot->Params.Gain, state->Gain[i]);
-}
-
-static ALvoid ALcompressorState_process(ALcompressorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    ALsizei i, j, k;
-    ALsizei base;
-
-    for(base = 0;base < SamplesToDo;)
-    {
-        ALfloat temps[64][4];
-        ALsizei td = mini(64, SamplesToDo-base);
-
-        /* Load samples into the temp buffer first. */
-        for(j = 0;j < 4;j++)
-        {
-            for(i = 0;i < td;i++)
-                temps[i][j] = SamplesIn[j][i+base];
-        }
-
-        if(state->Enabled)
-        {
-            ALfloat gain = state->GainCtrl;
-            ALfloat output, amplitude;
-
-            for(i = 0;i < td;i++)
-            {
-                /* Roughly calculate the maximum amplitude from the 4-channel
-                 * signal, and attack or release the gain control to reach it.
-                 */
-                amplitude = fabsf(temps[i][0]);
-                amplitude = maxf(amplitude + fabsf(temps[i][1]),
-                                 maxf(amplitude + fabsf(temps[i][2]),
-                                      amplitude + fabsf(temps[i][3])));
-                if(amplitude > gain)
-                    gain = minf(gain+state->AttackRate, amplitude);
-                else if(amplitude < gain)
-                    gain = maxf(gain-state->ReleaseRate, amplitude);
-
-                /* Apply the inverse of the gain control to normalize/compress
-                 * the volume. */
-                output = 1.0f / clampf(gain, 0.5f, 2.0f);
-                for(j = 0;j < 4;j++)
-                    temps[i][j] *= output;
-            }
-
-            state->GainCtrl = gain;
-        }
-        else
-        {
-            ALfloat gain = state->GainCtrl;
-            ALfloat output, amplitude;
-
-            for(i = 0;i < td;i++)
-            {
-                /* Same as above, except the amplitude is forced to 1. This
-                 * helps ensure smooth gain changes when the compressor is
-                 * turned on and off.
-                 */
-                amplitude = 1.0f;
-                if(amplitude > gain)
-                    gain = minf(gain+state->AttackRate, amplitude);
-                else if(amplitude < gain)
-                    gain = maxf(gain-state->ReleaseRate, amplitude);
-
-                output = 1.0f / clampf(gain, 0.5f, 2.0f);
-                for(j = 0;j < 4;j++)
-                    temps[i][j] *= output;
-            }
-
-            state->GainCtrl = gain;
-        }
-
-        /* Now mix to the output. */
-        for(j = 0;j < 4;j++)
-        {
-            for(k = 0;k < NumChannels;k++)
-            {
-                ALfloat gain = state->Gain[j][k];
-                if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
-                    continue;
-
-                for(i = 0;i < td;i++)
-                    SamplesOut[k][base+i] += gain * temps[i][j];
-            }
-        }
-
-        base += td;
-    }
-}
-
-
-typedef struct CompressorStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} CompressorStateFactory;
-
-static ALeffectState *CompressorStateFactory_create(CompressorStateFactory *UNUSED(factory))
-{
-    ALcompressorState *state;
-
-    NEW_OBJ0(state, ALcompressorState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(CompressorStateFactory);
-
-EffectStateFactory *CompressorStateFactory_getFactory(void)
-{
-    static CompressorStateFactory CompressorFactory = { { GET_VTABLE2(CompressorStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &CompressorFactory);
-}
-
-
-void ALcompressor_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_COMPRESSOR_ONOFF:
-            if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range");
-            props->Compressor.OnOff = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
-                       param);
-    }
-}
-void ALcompressor_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALcompressor_setParami(effect, context, param, vals[0]); }
-void ALcompressor_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
-void ALcompressor_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
-
-void ALcompressor_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{ 
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_COMPRESSOR_ONOFF:
-            *val = props->Compressor.OnOff;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
-                       param);
-    }
-}
-void ALcompressor_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALcompressor_getParami(effect, context, param, vals); }
-void ALcompressor_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
-void ALcompressor_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
-
-DEFINE_ALEFFECT_VTABLE(ALcompressor);

+ 168 - 0
Engine/lib/openal-soft/Alc/effects/compressor.cpp

@@ -0,0 +1,168 @@
+/**
+ * OpenAL 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.
+ *
+ * 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 <cstdlib>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "effectslot.h"
+#include "vecmat.h"
+
+
+namespace {
+
+#define AMP_ENVELOPE_MIN  0.5f
+#define AMP_ENVELOPE_MAX  2.0f
+
+#define ATTACK_TIME  0.1f /* 100ms to rise from min to max */
+#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
+
+
+struct CompressorState final : public EffectState {
+    /* Effect gains for each channel */
+    float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{};
+
+    /* Effect parameters */
+    bool mEnabled{true};
+    float mAttackMult{1.0f};
+    float mReleaseMult{1.0f};
+    float mEnvFollower{1.0f};
+
+
+    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
+    void update(const ALCcontext *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;
+
+    DEF_NEWDEL(CompressorState)
+};
+
+void CompressorState::deviceUpdate(const ALCdevice *device, const Buffer&)
+{
+    /* Number of samples to do a full attack and release (non-integer sample
+     * counts are okay).
+     */
+    const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME};
+    const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME};
+
+    /* Calculate per-sample multipliers to attack and release at the desired
+     * rates.
+     */
+    mAttackMult  = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
+    mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
+}
+
+void CompressorState::update(const ALCcontext*, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    mEnabled = props->Compressor.OnOff;
+
+    mOutTarget = target.Main->Buffer;
+    auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs)
+    { ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); };
+    SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains);
+}
+
+void CompressorState::process(const size_t samplesToDo,
+    const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        float gains[256];
+        const size_t td{minz(256, samplesToDo-base)};
+
+        /* Generate the per-sample gains from the signal envelope. */
+        float env{mEnvFollower};
+        if(mEnabled)
+        {
+            for(size_t i{0u};i < td;++i)
+            {
+                /* Clamp the absolute amplitude to the defined envelope limits,
+                 * then attack or release the envelope to reach it.
+                 */
+                const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
+                    AMP_ENVELOPE_MAX)};
+                if(amplitude > env)
+                    env = minf(env*mAttackMult, amplitude);
+                else if(amplitude < env)
+                    env = maxf(env*mReleaseMult, amplitude);
+
+                /* Apply the reciprocal of the envelope to normalize the volume
+                 * (compress the dynamic range).
+                 */
+                gains[i] = 1.0f / env;
+            }
+        }
+        else
+        {
+            /* Same as above, except the amplitude is forced to 1. This helps
+             * ensure smooth gain changes when the compressor is turned on and
+             * off.
+             */
+            for(size_t i{0u};i < td;++i)
+            {
+                const float amplitude{1.0f};
+                if(amplitude > env)
+                    env = minf(env*mAttackMult, amplitude);
+                else if(amplitude < env)
+                    env = maxf(env*mReleaseMult, amplitude);
+
+                gains[i] = 1.0f / env;
+            }
+        }
+        mEnvFollower = env;
+
+        /* Now compress the signal amplitude to output. */
+        auto changains = std::addressof(mGain[0]);
+        for(const auto &input : samplesIn)
+        {
+            const float *outgains{*(changains++)};
+            for(FloatBufferLine &output : samplesOut)
+            {
+                const float gain{*(outgains++)};
+                if(!(std::fabs(gain) > GainSilenceThreshold))
+                    continue;
+
+                for(size_t i{0u};i < td;i++)
+                    output[base+i] += input[base+i] * gains[i] * gain;
+            }
+        }
+
+        base += td;
+    }
+}
+
+
+struct CompressorStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new CompressorState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *CompressorStateFactory_getFactory()
+{
+    static CompressorStateFactory CompressorFactory{};
+    return &CompressorFactory;
+}

+ 567 - 0
Engine/lib/openal-soft/Alc/effects/convolution.cpp

@@ -0,0 +1,567 @@
+
+#include "config.h"
+
+#include <stdint.h>
+
+#ifdef HAVE_SSE_INTRINSICS
+#include <xmmintrin.h>
+#elif defined(HAVE_NEON)
+#include <arm_neon.h>
+#endif
+
+#include "alcmain.h"
+#include "alcomplex.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "bformatdec.h"
+#include "buffer_storage.h"
+#include "core/ambidefs.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 "polyphase_resampler.h"
+
+
+namespace {
+
+/* Convolution reverb is implemented using a segmented overlap-add method. The
+ * impulse response is broken up into multiple segments of 128 samples, and
+ * each segment has an FFT applied with a 256-sample buffer (the latter half
+ * left silent) to get its frequency-domain response. The resulting response
+ * has its positive/non-mirrored frequencies saved (129 bins) in each segment.
+ *
+ * Input samples are similarly broken up into 128-sample segments, with an FFT
+ * applied to each new incoming segment to get its 129 bins. A history of FFT'd
+ * input segments is maintained, equal to the length of the impulse response.
+ *
+ * To apply the reverberation, each impulse response segment is convolved with
+ * its paired input segment (using complex multiplies, far cheaper than FIRs),
+ * accumulating into a 256-bin FFT buffer. The input history is then shifted to
+ * align with later impulse response segments for next time.
+ *
+ * An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
+ * sample time-domain response for output, which is split in two halves. The
+ * first half is the 128-sample output, and the second half is a 128-sample
+ * (really, 127) delayed extension, which gets added to the output next time.
+ * Convolving two time-domain responses of lengths N and M results in a time-
+ * domain signal of length N+M-1, and this holds true regardless of the
+ * convolution being applied in the frequency domain, so these "overflow"
+ * samples need to be accounted for.
+ *
+ * To avoid a delay with gathering enough input samples to apply an FFT with,
+ * the first segment is applied directly in the time-domain as the samples come
+ * in. Once enough have been retrieved, the FFT is applied on the input and
+ * it's paired with the remaining (FFT'd) filter segments for processing.
+ */
+
+
+void LoadSamples(double *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
+}
+
+
+inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
+{
+    if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa();
+    if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D();
+    return AmbiScale::FromN3D();
+}
+
+inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
+{
+    if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
+    return AmbiIndex::FromACN();
+}
+
+inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
+{
+    if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
+    return AmbiIndex::FromACN2D();
+}
+
+
+struct ChanMap {
+    Channel channel;
+    float angle;
+    float elevation;
+};
+
+using complex_d = std::complex<double>;
+
+constexpr size_t ConvolveUpdateSize{256};
+constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
+
+
+void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter)
+{
+#ifdef HAVE_SSE_INTRINSICS
+    for(float &output : dst)
+    {
+        __m128 r4{_mm_setzero_ps()};
+        for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
+        {
+            const __m128 coeffs{_mm_load_ps(&filter[j])};
+            const __m128 s{_mm_loadu_ps(&src[j])};
+
+            r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
+        }
+        r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
+        r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
+        output = _mm_cvtss_f32(r4);
+
+        ++src;
+    }
+
+#elif defined(HAVE_NEON)
+
+    for(float &output : dst)
+    {
+        float32x4_t r4{vdupq_n_f32(0.0f)};
+        for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
+            r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j]));
+        r4 = vaddq_f32(r4, vrev64q_f32(r4));
+        output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
+
+        ++src;
+    }
+
+#else
+
+    for(float &output : dst)
+    {
+        float ret{0.0f};
+        for(size_t j{0};j < ConvolveUpdateSamples;++j)
+            ret += src[j] * filter[j];
+        output = ret;
+        ++src;
+    }
+#endif
+}
+
+struct ConvolutionState final : public EffectState {
+    FmtChannels mChannels{};
+    AmbiLayout mAmbiLayout{};
+    AmbiScaling mAmbiScaling{};
+    uint mAmbiOrder{};
+
+    size_t mFifoPos{0};
+    std::array<float,ConvolveUpdateSamples*2> mInput{};
+    al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
+    al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
+
+    alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{};
+
+    size_t mCurrentSegment{0};
+    size_t mNumConvolveSegs{0};
+
+    struct ChannelData {
+        alignas(16) FloatBufferLine mBuffer{};
+        float mHfScale{};
+        BandSplitter mFilter{};
+        float Current[MAX_OUTPUT_CHANNELS]{};
+        float Target[MAX_OUTPUT_CHANNELS]{};
+    };
+    using ChannelDataArray = al::FlexArray<ChannelData>;
+    std::unique_ptr<ChannelDataArray> mChans;
+    std::unique_ptr<complex_d[]> mComplexData;
+
+
+    ConvolutionState() = default;
+    ~ConvolutionState() override = default;
+
+    void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
+    void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
+    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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(ConvolutionState)
+};
+
+void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
+    const size_t samplesToDo)
+{
+    for(auto &chan : *mChans)
+        MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target,
+            samplesToDo, 0);
+}
+
+void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
+    const size_t samplesToDo)
+{
+    for(auto &chan : *mChans)
+    {
+        const al::span<float> src{chan.mBuffer.data(), samplesToDo};
+        chan.mFilter.processHfScale(src, chan.mHfScale);
+        MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
+    }
+}
+
+
+void ConvolutionState::deviceUpdate(const ALCdevice *device, const Buffer &buffer)
+{
+    constexpr uint MaxConvolveAmbiOrder{1u};
+
+    mFifoPos = 0;
+    mInput.fill(0.0f);
+    decltype(mFilter){}.swap(mFilter);
+    decltype(mOutput){}.swap(mOutput);
+    mFftBuffer.fill(complex_d{});
+
+    mCurrentSegment = 0;
+    mNumConvolveSegs = 0;
+
+    mChans = nullptr;
+    mComplexData = nullptr;
+
+    /* An empty buffer doesn't need a convolution filter. */
+    if(!buffer.storage || buffer.storage->mSampleLen < 1) return;
+
+    constexpr size_t m{ConvolveUpdateSize/2 + 1};
+    auto bytesPerSample = BytesFromFmt(buffer.storage->mType);
+    auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder);
+    auto numChannels = ChannelsFromFmt(buffer.storage->mChannels,
+        minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder));
+
+    mChans = ChannelDataArray::Create(numChannels);
+
+    /* The impulse response needs to have the same sample rate as the input and
+     * output. The bsinc24 resampler is decent, but there is high-frequency
+     * attenation that some people may be able to pick up on. Since this is
+     * called very infrequently, go ahead and use the polyphase resampler.
+     */
+    PPhaseResampler resampler;
+    if(device->Frequency != buffer.storage->mSampleRate)
+        resampler.init(buffer.storage->mSampleRate, device->Frequency);
+    const auto resampledCount = static_cast<uint>(
+        (uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) /
+        buffer.storage->mSampleRate);
+
+    const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
+    for(auto &e : *mChans)
+        e.mFilter = splitter;
+
+    mFilter.resize(numChannels, {});
+    mOutput.resize(numChannels, {});
+
+    /* Calculate the number of segments needed to hold the impulse response and
+     * the input history (rounded up), and allocate them. Exclude one segment
+     * which gets applied as a time-domain FIR filter. Make sure at least one
+     * segment is allocated to simplify handling.
+     */
+    mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
+    mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
+
+    const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
+    mComplexData = std::make_unique<complex_d[]>(complex_length);
+    std::fill_n(mComplexData.get(), complex_length, complex_d{});
+
+    mChannels = buffer.storage->mChannels;
+    mAmbiLayout = buffer.storage->mAmbiLayout;
+    mAmbiScaling = buffer.storage->mAmbiScaling;
+    mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder);
+
+    auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount));
+    complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m;
+    for(size_t c{0};c < numChannels;++c)
+    {
+        /* Load the samples from the buffer, and resample to match the device. */
+        LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels,
+            buffer.storage->mType, buffer.storage->mSampleLen);
+        if(device->Frequency != buffer.storage->mSampleRate)
+            resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount,
+                srcsamples.get());
+
+        /* Store the first segment's samples in reverse in the time-domain, to
+         * apply as a FIR filter.
+         */
+        const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)};
+        std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(),
+            [](const double d) noexcept -> float { return static_cast<float>(d); });
+
+        size_t done{first_size};
+        for(size_t s{0};s < mNumConvolveSegs;++s)
+        {
+            const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
+
+            auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin());
+            done += todo;
+            std::fill(iter, mFftBuffer.end(), complex_d{});
+
+            forward_fft(mFftBuffer);
+            filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter);
+        }
+    }
+}
+
+
+void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps* /*props*/, const EffectTarget target)
+{
+    /* NOTE: Stereo and Rear are slightly different from normal mixing (as
+     * defined in alu.cpp). These are 45 degrees from center, rather than the
+     * 30 degrees used there.
+     *
+     * TODO: LFE is not mixed to output. This will require each buffer channel
+     * to have its own output target since the main mixing buffer won't have an
+     * LFE channel (due to being B-Format).
+     */
+    static const ChanMap MonoMap[1]{
+        { FrontCenter, 0.0f, 0.0f }
+    }, StereoMap[2]{
+        { FrontLeft,  Deg2Rad(-45.0f), Deg2Rad(0.0f) },
+        { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
+    }, RearMap[2]{
+        { BackLeft,  Deg2Rad(-135.0f), Deg2Rad(0.0f) },
+        { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
+    }, QuadMap[4]{
+        { FrontLeft,  Deg2Rad( -45.0f), Deg2Rad(0.0f) },
+        { FrontRight, Deg2Rad(  45.0f), Deg2Rad(0.0f) },
+        { BackLeft,   Deg2Rad(-135.0f), Deg2Rad(0.0f) },
+        { BackRight,  Deg2Rad( 135.0f), Deg2Rad(0.0f) }
+    }, X51Map[6]{
+        { FrontLeft,   Deg2Rad( -30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad(  30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(   0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { SideLeft,    Deg2Rad(-110.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad( 110.0f), Deg2Rad(0.0f) }
+    }, X61Map[7]{
+        { FrontLeft,   Deg2Rad(-30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad( 30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(  0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { BackCenter,  Deg2Rad(180.0f), Deg2Rad(0.0f) },
+        { SideLeft,    Deg2Rad(-90.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad( 90.0f), Deg2Rad(0.0f) }
+    }, X71Map[8]{
+        { FrontLeft,   Deg2Rad( -30.0f), Deg2Rad(0.0f) },
+        { FrontRight,  Deg2Rad(  30.0f), Deg2Rad(0.0f) },
+        { FrontCenter, Deg2Rad(   0.0f), Deg2Rad(0.0f) },
+        { LFE, 0.0f, 0.0f },
+        { BackLeft,    Deg2Rad(-150.0f), Deg2Rad(0.0f) },
+        { BackRight,   Deg2Rad( 150.0f), Deg2Rad(0.0f) },
+        { SideLeft,    Deg2Rad( -90.0f), Deg2Rad(0.0f) },
+        { SideRight,   Deg2Rad(  90.0f), Deg2Rad(0.0f) }
+    };
+
+    if(mNumConvolveSegs < 1)
+        return;
+
+    mMix = &ConvolutionState::NormalMix;
+
+    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)
+    {
+        ALCdevice *device{context->mDevice.get()};
+        if(device->mAmbiOrder > mAmbiOrder)
+        {
+            mMix = &ConvolutionState::UpsampleMix;
+            const auto scales = BFormatDec::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
+            (*mChans)[0].mHfScale = scales[0];
+            for(size_t i{1};i < mChans->size();++i)
+                (*mChans)[i].mHfScale = scales[1];
+        }
+        mOutTarget = target.Main->Buffer;
+
+        auto&& scales = GetAmbiScales(mAmbiScaling);
+        const uint8_t *index_map{(mChannels == FmtBFormat2D) ?
+            GetAmbi2DLayout(mAmbiLayout).data() :
+            GetAmbiLayout(mAmbiLayout).data()};
+
+        std::array<float,MaxAmbiChannels> coeffs{};
+        for(size_t c{0u};c < mChans->size();++c)
+        {
+            const size_t acn{index_map[c]};
+            coeffs[acn] = scales[acn];
+            ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target);
+            coeffs[acn] = 0.0f;
+        }
+    }
+    else
+    {
+        ALCdevice *device{context->mDevice.get()};
+        al::span<const ChanMap> chanmap{};
+        switch(mChannels)
+        {
+        case FmtMono: chanmap = MonoMap; break;
+        case FmtStereo: chanmap = StereoMap; break;
+        case FmtRear: chanmap = RearMap; break;
+        case FmtQuad: chanmap = QuadMap; break;
+        case FmtX51: chanmap = X51Map; break;
+        case FmtX61: chanmap = X61Map; break;
+        case FmtX71: chanmap = X71Map; break;
+        case FmtBFormat2D:
+        case FmtBFormat3D:
+            break;
+        }
+
+        mOutTarget = target.Main->Buffer;
+        if(device->mRenderMode == RenderMode::Pairwise)
+        {
+            auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
+            {
+                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);
+                return azimuth;
+            };
+
+            for(size_t i{0};i < chanmap.size();++i)
+            {
+                if(chanmap[i].channel == LFE) continue;
+                const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f),
+                    chanmap[i].elevation, 0.0f);
+                ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
+            }
+        }
+        else for(size_t i{0};i < chanmap.size();++i)
+        {
+            if(chanmap[i].channel == LFE) continue;
+            const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f);
+            ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
+        }
+    }
+}
+
+void ConvolutionState::process(const size_t samplesToDo,
+    const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    if(mNumConvolveSegs < 1)
+        return;
+
+    constexpr size_t m{ConvolveUpdateSize/2 + 1};
+    size_t curseg{mCurrentSegment};
+    auto &chans = *mChans;
+
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
+
+        std::copy_n(samplesIn[0].begin() + base, todo,
+            mInput.begin()+ConvolveUpdateSamples+mFifoPos);
+
+        /* Apply the FIR for the newly retrieved input samples, and combine it
+         * with the inverse FFT'd output samples.
+         */
+        for(size_t c{0};c < chans.size();++c)
+        {
+            auto buf_iter = chans[c].mBuffer.begin() + base;
+            apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos,
+                mFilter[c].data());
+
+            auto fifo_iter = mOutput[c].begin() + mFifoPos;
+            std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
+        }
+
+        mFifoPos += todo;
+        base += todo;
+
+        /* Check whether the input buffer is filled with new samples. */
+        if(mFifoPos < ConvolveUpdateSamples) break;
+        mFifoPos = 0;
+
+        /* Move the newest input to the front for the next iteration's history. */
+        std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
+
+        /* Calculate the frequency domain response and add the relevant
+         * frequency bins to the FFT history.
+         */
+        auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
+        std::fill(fftiter, mFftBuffer.end(), complex_d{});
+        forward_fft(mFftBuffer);
+
+        std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
+
+        const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
+        for(size_t c{0};c < chans.size();++c)
+        {
+            std::fill_n(mFftBuffer.begin(), m, complex_d{});
+
+            /* Convolve each input segment with its IR filter counterpart
+             * (aligned in time).
+             */
+            const complex_d *RESTRICT input{&mComplexData[curseg*m]};
+            for(size_t s{curseg};s < mNumConvolveSegs;++s)
+            {
+                for(size_t i{0};i < m;++i,++input,++filter)
+                    mFftBuffer[i] += *input * *filter;
+            }
+            input = mComplexData.get();
+            for(size_t s{0};s < curseg;++s)
+            {
+                for(size_t i{0};i < m;++i,++input,++filter)
+                    mFftBuffer[i] += *input * *filter;
+            }
+
+            /* Reconstruct the mirrored/negative frequencies to do a proper
+             * inverse FFT.
+             */
+            for(size_t i{m};i < ConvolveUpdateSize;++i)
+                mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]);
+
+            /* Apply iFFT to get the 256 (really 255) samples for output. The
+             * 128 output samples are combined with the last output's 127
+             * second-half samples (and this output's second half is
+             * subsequently saved for next time).
+             */
+            inverse_fft(mFftBuffer);
+
+            /* The iFFT'd response is scaled up by the number of bins, so apply
+             * the inverse to normalize the output.
+             */
+            for(size_t i{0};i < ConvolveUpdateSamples;++i)
+                mOutput[c][i] =
+                    static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) +
+                    mOutput[c][ConvolveUpdateSamples+i];
+            for(size_t i{0};i < ConvolveUpdateSamples;++i)
+                mOutput[c][ConvolveUpdateSamples+i] =
+                    static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() *
+                        (1.0/double{ConvolveUpdateSize}));
+        }
+
+        /* Shift the input history. */
+        curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1);
+    }
+    mCurrentSegment = curseg;
+
+    /* Finally, mix to the output. */
+    (this->*mMix)(samplesOut, samplesToDo);
+}
+
+
+struct ConvolutionStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *ConvolutionStateFactory_getFactory()
+{
+    static ConvolutionStateFactory ConvolutionFactory{};
+    return &ConvolutionFactory;
+}

+ 0 - 184
Engine/lib/openal-soft/Alc/effects/dedicated.c

@@ -1,184 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2011 by Chris Robinson.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-typedef struct ALdedicatedState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
-    ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
-} ALdedicatedState;
-
-static ALvoid ALdedicatedState_Destruct(ALdedicatedState *state);
-static ALboolean ALdedicatedState_deviceUpdate(ALdedicatedState *state, ALCdevice *device);
-static ALvoid ALdedicatedState_update(ALdedicatedState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALdedicatedState_process(ALdedicatedState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALdedicatedState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALdedicatedState);
-
-
-static void ALdedicatedState_Construct(ALdedicatedState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALdedicatedState, ALeffectState, state);
-}
-
-static ALvoid ALdedicatedState_Destruct(ALdedicatedState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALdedicatedState_deviceUpdate(ALdedicatedState *state, ALCdevice *UNUSED(device))
-{
-    ALsizei i;
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        state->CurrentGains[i] = 0.0f;
-    return AL_TRUE;
-}
-
-static ALvoid ALdedicatedState_update(ALdedicatedState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALfloat Gain;
-    ALsizei i;
-
-    for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
-        state->TargetGains[i] = 0.0f;
-
-    Gain = slot->Params.Gain * props->Dedicated.Gain;
-    if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
-    {
-        int idx;
-        if((idx=GetChannelIdxByName(&device->RealOut, LFE)) != -1)
-        {
-            STATIC_CAST(ALeffectState,state)->OutBuffer = device->RealOut.Buffer;
-            STATIC_CAST(ALeffectState,state)->OutChannels = device->RealOut.NumChannels;
-            state->TargetGains[idx] = Gain;
-        }
-    }
-    else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
-    {
-        int idx;
-        /* Dialog goes to the front-center speaker if it exists, otherwise it
-         * plays from the front-center location. */
-        if((idx=GetChannelIdxByName(&device->RealOut, FrontCenter)) != -1)
-        {
-            STATIC_CAST(ALeffectState,state)->OutBuffer = device->RealOut.Buffer;
-            STATIC_CAST(ALeffectState,state)->OutChannels = device->RealOut.NumChannels;
-            state->TargetGains[idx] = Gain;
-        }
-        else
-        {
-            ALfloat coeffs[MAX_AMBI_COEFFS];
-            CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
-
-            STATIC_CAST(ALeffectState,state)->OutBuffer = device->Dry.Buffer;
-            STATIC_CAST(ALeffectState,state)->OutChannels = device->Dry.NumChannels;
-            ComputeDryPanGains(&device->Dry, coeffs, Gain, state->TargetGains);
-        }
-    }
-}
-
-static ALvoid ALdedicatedState_process(ALdedicatedState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    MixSamples(SamplesIn[0], NumChannels, SamplesOut, state->CurrentGains,
-               state->TargetGains, SamplesToDo, 0, SamplesToDo);
-}
-
-
-typedef struct DedicatedStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} DedicatedStateFactory;
-
-ALeffectState *DedicatedStateFactory_create(DedicatedStateFactory *UNUSED(factory))
-{
-    ALdedicatedState *state;
-
-    NEW_OBJ0(state, ALdedicatedState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(DedicatedStateFactory);
-
-
-EffectStateFactory *DedicatedStateFactory_getFactory(void)
-{
-    static DedicatedStateFactory DedicatedFactory = { { GET_VTABLE2(DedicatedStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &DedicatedFactory);
-}
-
-
-void ALdedicated_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
-void ALdedicated_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
-void ALdedicated_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_DEDICATED_GAIN:
-            if(!(val >= 0.0f && isfinite(val)))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
-            props->Dedicated.Gain = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
-    }
-}
-void ALdedicated_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALdedicated_setParamf(effect, context, param, vals[0]); }
-
-void ALdedicated_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
-void ALdedicated_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
-void ALdedicated_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_DEDICATED_GAIN:
-            *val = props->Dedicated.Gain;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
-    }
-}
-void ALdedicated_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALdedicated_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALdedicated);

+ 110 - 0
Engine/lib/openal-soft/Alc/effects/dedicated.cpp

@@ -0,0 +1,110 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2011 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cstdlib>
+#include <cmath>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "effectslot.h"
+
+
+namespace {
+
+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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(DedicatedState)
+};
+
+void DedicatedState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+}
+
+void DedicatedState::update(const ALCcontext*, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+    const float Gain{slot->Gain * props->Dedicated.Gain};
+
+    if(slot->EffectType == EffectSlotType::DedicatedLFE)
+    {
+        const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
+            GetChannelIdxByName(*target.RealOut, LFE)};
+        if(idx != INVALID_CHANNEL_INDEX)
+        {
+            mOutTarget = target.RealOut->Buffer;
+            mTargetGains[idx] = Gain;
+        }
+    }
+    else if(slot->EffectType == EffectSlotType::DedicatedDialog)
+    {
+        /* Dialog goes to the front-center speaker if it exists, otherwise it
+         * plays from the front-center location. */
+        const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
+            GetChannelIdxByName(*target.RealOut, FrontCenter)};
+        if(idx != INVALID_CHANNEL_INDEX)
+        {
+            mOutTarget = target.RealOut->Buffer;
+            mTargetGains[idx] = Gain;
+        }
+        else
+        {
+            const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
+
+            mOutTarget = target.Main->Buffer;
+            ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
+        }
+    }
+}
+
+void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
+        samplesToDo, 0);
+}
+
+
+struct DedicatedStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new DedicatedState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *DedicatedStateFactory_getFactory()
+{
+    static DedicatedStateFactory DedicatedFactory{};
+    return &DedicatedFactory;
+}

+ 0 - 287
Engine/lib/openal-soft/Alc/effects/distortion.c

@@ -1,287 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2013 by Mike Gorchak
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-typedef struct ALdistortionState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    /* Effect gains for each channel */
-    ALfloat Gain[MAX_OUTPUT_CHANNELS];
-
-    /* Effect parameters */
-    BiquadFilter lowpass;
-    BiquadFilter bandpass;
-    ALfloat attenuation;
-    ALfloat edge_coeff;
-
-    ALfloat Buffer[2][BUFFERSIZE];
-} ALdistortionState;
-
-static ALvoid ALdistortionState_Destruct(ALdistortionState *state);
-static ALboolean ALdistortionState_deviceUpdate(ALdistortionState *state, ALCdevice *device);
-static ALvoid ALdistortionState_update(ALdistortionState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALdistortionState_process(ALdistortionState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALdistortionState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALdistortionState);
-
-
-static void ALdistortionState_Construct(ALdistortionState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALdistortionState, ALeffectState, state);
-}
-
-static ALvoid ALdistortionState_Destruct(ALdistortionState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALdistortionState_deviceUpdate(ALdistortionState *state, ALCdevice *UNUSED(device))
-{
-    BiquadFilter_clear(&state->lowpass);
-    BiquadFilter_clear(&state->bandpass);
-    return AL_TRUE;
-}
-
-static ALvoid ALdistortionState_update(ALdistortionState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALfloat frequency = (ALfloat)device->Frequency;
-    ALfloat coeffs[MAX_AMBI_COEFFS];
-    ALfloat bandwidth;
-    ALfloat cutoff;
-    ALfloat edge;
-
-    /* Store waveshaper edge settings. */
-    edge = sinf(props->Distortion.Edge * (F_PI_2));
-    edge = minf(edge, 0.99f);
-    state->edge_coeff = 2.0f * edge / (1.0f-edge);
-
-    cutoff = props->Distortion.LowpassCutoff;
-    /* Bandwidth value is constant in octaves. */
-    bandwidth = (cutoff / 2.0f) / (cutoff * 0.67f);
-    /* Multiply sampling frequency by the amount of oversampling done during
-     * processing.
-     */
-    BiquadFilter_setParams(&state->lowpass, BiquadType_LowPass, 1.0f,
-        cutoff / (frequency*4.0f), calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
-    );
-
-    cutoff = props->Distortion.EQCenter;
-    /* Convert bandwidth in Hz to octaves. */
-    bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
-    BiquadFilter_setParams(&state->bandpass, BiquadType_BandPass, 1.0f,
-        cutoff / (frequency*4.0f), calc_rcpQ_from_bandwidth(cutoff / (frequency*4.0f), bandwidth)
-    );
-
-    CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
-    ComputeDryPanGains(&device->Dry, coeffs, slot->Params.Gain * props->Distortion.Gain,
-                       state->Gain);
-}
-
-static ALvoid ALdistortionState_process(ALdistortionState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    ALfloat (*restrict buffer)[BUFFERSIZE] = state->Buffer;
-    const ALfloat fc = state->edge_coeff;
-    ALsizei base;
-    ALsizei i, k;
-
-    for(base = 0;base < SamplesToDo;)
-    {
-        /* Perform 4x oversampling to avoid aliasing. Oversampling greatly
-         * improves distortion quality and allows to implement lowpass and
-         * bandpass filters using high frequencies, at which classic IIR
-         * filters became unstable.
-         */
-        ALsizei todo = mini(BUFFERSIZE, (SamplesToDo-base) * 4);
-
-        /* Fill oversample buffer using zero stuffing. Multiply the sample by
-         * the amount of oversampling to maintain the signal's power.
-         */
-        for(i = 0;i < todo;i++)
-            buffer[0][i] = !(i&3) ? SamplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
-
-        /* First step, do lowpass filtering of original signal. Additionally
-         * perform buffer interpolation and lowpass cutoff for oversampling
-         * (which is fortunately first step of distortion). So combine three
-         * operations into the one.
-         */
-        BiquadFilter_process(&state->lowpass, buffer[1], buffer[0], todo);
-
-        /* Second step, do distortion using waveshaper function to emulate
-         * signal processing during tube overdriving. Three steps of
-         * waveshaping are intended to modify waveform without boost/clipping/
-         * attenuation process.
-         */
-        for(i = 0;i < todo;i++)
-        {
-            ALfloat smp = buffer[1][i];
-
-            smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
-            smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp)) * -1.0f;
-            smp = (1.0f + fc) * smp/(1.0f + fc*fabsf(smp));
-
-            buffer[0][i] = smp;
-        }
-
-        /* Third step, do bandpass filtering of distorted signal. */
-        BiquadFilter_process(&state->bandpass, buffer[1], buffer[0], todo);
-
-        todo >>= 2;
-        for(k = 0;k < NumChannels;k++)
-        {
-            /* Fourth step, final, do attenuation and perform decimation,
-             * storing only one sample out of four.
-             */
-            ALfloat gain = state->Gain[k];
-            if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
-                continue;
-
-            for(i = 0;i < todo;i++)
-                SamplesOut[k][base+i] += gain * buffer[1][i*4];
-        }
-
-        base += todo;
-    }
-}
-
-
-typedef struct DistortionStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} DistortionStateFactory;
-
-static ALeffectState *DistortionStateFactory_create(DistortionStateFactory *UNUSED(factory))
-{
-    ALdistortionState *state;
-
-    NEW_OBJ0(state, ALdistortionState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(DistortionStateFactory);
-
-
-EffectStateFactory *DistortionStateFactory_getFactory(void)
-{
-    static DistortionStateFactory DistortionFactory = { { GET_VTABLE2(DistortionStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &DistortionFactory);
-}
-
-
-void ALdistortion_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
-void ALdistortion_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
-void ALdistortion_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_DISTORTION_EDGE:
-            if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range");
-            props->Distortion.Edge = val;
-            break;
-
-        case AL_DISTORTION_GAIN:
-            if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range");
-            props->Distortion.Gain = val;
-            break;
-
-        case AL_DISTORTION_LOWPASS_CUTOFF:
-            if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range");
-            props->Distortion.LowpassCutoff = val;
-            break;
-
-        case AL_DISTORTION_EQCENTER:
-            if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range");
-            props->Distortion.EQCenter = val;
-            break;
-
-        case AL_DISTORTION_EQBANDWIDTH:
-            if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range");
-            props->Distortion.EQBandwidth = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
-                       param);
-    }
-}
-void ALdistortion_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALdistortion_setParamf(effect, context, param, vals[0]); }
-
-void ALdistortion_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
-void ALdistortion_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
-void ALdistortion_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_DISTORTION_EDGE:
-            *val = props->Distortion.Edge;
-            break;
-
-        case AL_DISTORTION_GAIN:
-            *val = props->Distortion.Gain;
-            break;
-
-        case AL_DISTORTION_LOWPASS_CUTOFF:
-            *val = props->Distortion.LowpassCutoff;
-            break;
-
-        case AL_DISTORTION_EQCENTER:
-            *val = props->Distortion.EQCenter;
-            break;
-
-        case AL_DISTORTION_EQBANDWIDTH:
-            *val = props->Distortion.EQBandwidth;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid distortion float property 0x%04x",
-                       param);
-    }
-}
-void ALdistortion_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALdistortion_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALdistortion);

+ 167 - 0
Engine/lib/openal-soft/Alc/effects/distortion.cpp

@@ -0,0 +1,167 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "core/filters/biquad.h"
+#include "effectslot.h"
+
+
+namespace {
+
+struct DistortionState final : public EffectState {
+    /* Effect gains for each channel */
+    float mGain[MAX_OUTPUT_CHANNELS]{};
+
+    /* Effect parameters */
+    BiquadFilter mLowpass;
+    BiquadFilter mBandpass;
+    float mAttenuation{};
+    float mEdgeCoeff{};
+
+    float mBuffer[2][BufferLineSize]{};
+
+
+    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
+    void update(const ALCcontext *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;
+
+    DEF_NEWDEL(DistortionState)
+};
+
+void DistortionState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    mLowpass.clear();
+    mBandpass.clear();
+}
+
+void DistortionState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+
+    /* Store waveshaper edge settings. */
+    const float edge{minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge),
+        0.99f)};
+    mEdgeCoeff = 2.0f * edge / (1.0f-edge);
+
+    float cutoff{props->Distortion.LowpassCutoff};
+    /* Bandwidth value is constant in octaves. */
+    float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
+    /* Divide normalized frequency by the amount of oversampling done during
+     * processing.
+     */
+    auto frequency = static_cast<float>(device->Frequency);
+    mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
+
+    cutoff = props->Distortion.EQCenter;
+    /* Convert bandwidth in Hz to octaves. */
+    bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
+    mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
+
+    const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
+
+    mOutTarget = target.Main->Buffer;
+    ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
+}
+
+void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    const float fc{mEdgeCoeff};
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        /* Perform 4x oversampling to avoid aliasing. Oversampling greatly
+         * improves distortion quality and allows to implement lowpass and
+         * bandpass filters using high frequencies, at which classic IIR
+         * filters became unstable.
+         */
+        size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
+
+        /* Fill oversample buffer using zero stuffing. Multiply the sample by
+         * the amount of oversampling to maintain the signal's power.
+         */
+        for(size_t i{0u};i < todo;i++)
+            mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
+
+        /* First step, do lowpass filtering of original signal. Additionally
+         * perform buffer interpolation and lowpass cutoff for oversampling
+         * (which is fortunately first step of distortion). So combine three
+         * operations into the one.
+         */
+        mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
+
+        /* Second step, do distortion using waveshaper function to emulate
+         * signal processing during tube overdriving. Three steps of
+         * waveshaping are intended to modify waveform without boost/clipping/
+         * attenuation process.
+         */
+        auto proc_sample = [fc](float smp) -> float
+        {
+            smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
+            smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
+            smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
+            return smp;
+        };
+        std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
+            proc_sample);
+
+        /* Third step, do bandpass filtering of distorted signal. */
+        mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
+
+        todo >>= 2;
+        const float *outgains{mGain};
+        for(FloatBufferLine &output : samplesOut)
+        {
+            /* Fourth step, final, do attenuation and perform decimation,
+             * storing only one sample out of four.
+             */
+            const float gain{*(outgains++)};
+            if(!(std::fabs(gain) > GainSilenceThreshold))
+                continue;
+
+            for(size_t i{0u};i < todo;i++)
+                output[base+i] += gain * mBuffer[1][i*4];
+        }
+
+        base += todo;
+    }
+}
+
+
+struct DistortionStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new DistortionState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *DistortionStateFactory_getFactory()
+{
+    static DistortionStateFactory DistortionFactory{};
+    return &DistortionFactory;
+}

+ 0 - 310
Engine/lib/openal-soft/Alc/effects/echo.c

@@ -1,310 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2009 by Chris Robinson.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alFilter.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-typedef struct ALechoState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    ALfloat *SampleBuffer;
-    ALsizei BufferLength;
-
-    // The echo is two tap. The delay is the number of samples from before the
-    // current offset
-    struct {
-        ALsizei delay;
-    } Tap[2];
-    ALsizei Offset;
-
-    /* The panning gains for the two taps */
-    struct {
-        ALfloat Current[MAX_OUTPUT_CHANNELS];
-        ALfloat Target[MAX_OUTPUT_CHANNELS];
-    } Gains[2];
-
-    ALfloat FeedGain;
-
-    BiquadFilter Filter;
-} ALechoState;
-
-static ALvoid ALechoState_Destruct(ALechoState *state);
-static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device);
-static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALechoState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALechoState);
-
-
-static void ALechoState_Construct(ALechoState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALechoState, ALeffectState, state);
-
-    state->BufferLength = 0;
-    state->SampleBuffer = NULL;
-
-    state->Tap[0].delay = 0;
-    state->Tap[1].delay = 0;
-    state->Offset = 0;
-
-    BiquadFilter_clear(&state->Filter);
-}
-
-static ALvoid ALechoState_Destruct(ALechoState *state)
-{
-    al_free(state->SampleBuffer);
-    state->SampleBuffer = NULL;
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALechoState_deviceUpdate(ALechoState *state, ALCdevice *Device)
-{
-    ALsizei maxlen;
-
-    // Use the next power of 2 for the buffer length, so the tap offsets can be
-    // wrapped using a mask instead of a modulo
-    maxlen = float2int(AL_ECHO_MAX_DELAY*Device->Frequency + 0.5f) +
-             float2int(AL_ECHO_MAX_LRDELAY*Device->Frequency + 0.5f);
-    maxlen = NextPowerOf2(maxlen);
-    if(maxlen <= 0) return AL_FALSE;
-
-    if(maxlen != state->BufferLength)
-    {
-        void *temp = al_calloc(16, maxlen * sizeof(ALfloat));
-        if(!temp) return AL_FALSE;
-
-        al_free(state->SampleBuffer);
-        state->SampleBuffer = temp;
-        state->BufferLength = maxlen;
-    }
-
-    memset(state->SampleBuffer, 0, state->BufferLength*sizeof(ALfloat));
-    memset(state->Gains, 0, sizeof(state->Gains));
-
-    return AL_TRUE;
-}
-
-static ALvoid ALechoState_update(ALechoState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALuint frequency = device->Frequency;
-    ALfloat coeffs[MAX_AMBI_COEFFS];
-    ALfloat gainhf, lrpan, spread;
-
-    state->Tap[0].delay = maxi(float2int(props->Echo.Delay*frequency + 0.5f), 1);
-    state->Tap[1].delay = float2int(props->Echo.LRDelay*frequency + 0.5f);
-    state->Tap[1].delay += state->Tap[0].delay;
-
-    spread = props->Echo.Spread;
-    if(spread < 0.0f) lrpan = -1.0f;
-    else lrpan = 1.0f;
-    /* Convert echo spread (where 0 = omni, +/-1 = directional) to coverage
-     * spread (where 0 = point, tau = omni).
-     */
-    spread = asinf(1.0f - fabsf(spread))*4.0f;
-
-    state->FeedGain = props->Echo.Feedback;
-
-    gainhf = maxf(1.0f - props->Echo.Damping, 0.0625f); /* Limit -24dB */
-    BiquadFilter_setParams(&state->Filter, BiquadType_HighShelf,
-        gainhf, LOWPASSFREQREF/frequency, calc_rcpQ_from_slope(gainhf, 1.0f)
-    );
-
-    /* First tap panning */
-    CalcAngleCoeffs(-F_PI_2*lrpan, 0.0f, spread, coeffs);
-    ComputeDryPanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[0].Target);
-
-    /* Second tap panning */
-    CalcAngleCoeffs( F_PI_2*lrpan, 0.0f, spread, coeffs);
-    ComputeDryPanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[1].Target);
-}
-
-static ALvoid ALechoState_process(ALechoState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    const ALsizei mask = state->BufferLength-1;
-    const ALsizei tap1 = state->Tap[0].delay;
-    const ALsizei tap2 = state->Tap[1].delay;
-    ALfloat *restrict delaybuf = state->SampleBuffer;
-    ALsizei offset = state->Offset;
-    ALfloat z1, z2, in, out;
-    ALsizei base;
-    ALsizei c, i;
-
-    z1 = state->Filter.z1;
-    z2 = state->Filter.z2;
-    for(base = 0;base < SamplesToDo;)
-    {
-        alignas(16) ALfloat temps[2][128];
-        ALsizei td = mini(128, SamplesToDo-base);
-
-        for(i = 0;i < td;i++)
-        {
-            /* Feed the delay buffer's input first. */
-            delaybuf[offset&mask] = SamplesIn[0][i+base];
-
-            /* First tap */
-            temps[0][i] = delaybuf[(offset-tap1) & mask];
-            /* Second tap */
-            temps[1][i] = delaybuf[(offset-tap2) & mask];
-
-            /* Apply damping to the second tap, then add it to the buffer with
-             * feedback attenuation.
-             */
-            in = temps[1][i];
-            out = in*state->Filter.b0 + z1;
-            z1 = in*state->Filter.b1 - out*state->Filter.a1 + z2;
-            z2 = in*state->Filter.b2 - out*state->Filter.a2;
-
-            delaybuf[offset&mask] += out * state->FeedGain;
-            offset++;
-        }
-
-        for(c = 0;c < 2;c++)
-            MixSamples(temps[c], NumChannels, SamplesOut, state->Gains[c].Current,
-                       state->Gains[c].Target, SamplesToDo-base, base, td);
-
-        base += td;
-    }
-    state->Filter.z1 = z1;
-    state->Filter.z2 = z2;
-
-    state->Offset = offset;
-}
-
-
-typedef struct EchoStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} EchoStateFactory;
-
-ALeffectState *EchoStateFactory_create(EchoStateFactory *UNUSED(factory))
-{
-    ALechoState *state;
-
-    NEW_OBJ0(state, ALechoState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(EchoStateFactory);
-
-EffectStateFactory *EchoStateFactory_getFactory(void)
-{
-    static EchoStateFactory EchoFactory = { { GET_VTABLE2(EchoStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &EchoFactory);
-}
-
-
-void ALecho_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
-void ALecho_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
-void ALecho_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_ECHO_DELAY:
-            if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
-            props->Echo.Delay = val;
-            break;
-
-        case AL_ECHO_LRDELAY:
-            if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
-            props->Echo.LRDelay = val;
-            break;
-
-        case AL_ECHO_DAMPING:
-            if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
-            props->Echo.Damping = val;
-            break;
-
-        case AL_ECHO_FEEDBACK:
-            if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
-            props->Echo.Feedback = val;
-            break;
-
-        case AL_ECHO_SPREAD:
-            if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
-            props->Echo.Spread = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
-    }
-}
-void ALecho_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALecho_setParamf(effect, context, param, vals[0]); }
-
-void ALecho_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
-void ALecho_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
-void ALecho_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_ECHO_DELAY:
-            *val = props->Echo.Delay;
-            break;
-
-        case AL_ECHO_LRDELAY:
-            *val = props->Echo.LRDelay;
-            break;
-
-        case AL_ECHO_DAMPING:
-            *val = props->Echo.Damping;
-            break;
-
-        case AL_ECHO_FEEDBACK:
-            *val = props->Echo.Feedback;
-            break;
-
-        case AL_ECHO_SPREAD:
-            *val = props->Echo.Spread;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
-    }
-}
-void ALecho_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALecho_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALecho);

+ 168 - 0
Engine/lib/openal-soft/Alc/effects/echo.cpp

@@ -0,0 +1,168 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "core/filters/biquad.h"
+#include "effectslot.h"
+#include "vector.h"
+
+
+namespace {
+
+constexpr float LowpassFreqRef{5000.0f};
+
+struct EchoState final : public EffectState {
+    al::vector<float,16> mSampleBuffer;
+
+    // The echo is two tap. The delay is the number of samples from before the
+    // current offset
+    struct {
+        size_t delay{0u};
+    } mTap[2];
+    size_t mOffset{0u};
+
+    /* The panning gains for the two taps */
+    struct {
+        float Current[MAX_OUTPUT_CHANNELS]{};
+        float Target[MAX_OUTPUT_CHANNELS]{};
+    } mGains[2];
+
+    BiquadFilter mFilter;
+    float mFeedGain{0.0f};
+
+    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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(EchoState)
+};
+
+void EchoState::deviceUpdate(const ALCdevice *Device, const Buffer&)
+{
+    const auto frequency = static_cast<float>(Device->Frequency);
+
+    // Use the next power of 2 for the buffer length, so the tap offsets can be
+    // wrapped using a mask instead of a modulo
+    const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) +
+        float2uint(EchoMaxLRDelay*frequency + 0.5f))};
+    if(maxlen != mSampleBuffer.size())
+        al::vector<float,16>(maxlen).swap(mSampleBuffer);
+
+    std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
+    for(auto &e : mGains)
+    {
+        std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
+        std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
+    }
+}
+
+void EchoState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+    const auto frequency = static_cast<float>(device->Frequency);
+
+    mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
+    mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
+
+    const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
+    mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
+
+    mFeedGain = props->Echo.Feedback;
+
+    /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
+    const float angle{std::asin(props->Echo.Spread)};
+
+    const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
+    const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f);
+
+    mOutTarget = target.Main->Buffer;
+    ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target);
+    ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target);
+}
+
+void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    const size_t mask{mSampleBuffer.size()-1};
+    float *RESTRICT delaybuf{mSampleBuffer.data()};
+    size_t offset{mOffset};
+    size_t tap1{offset - mTap[0].delay};
+    size_t tap2{offset - mTap[1].delay};
+    float z1, z2;
+
+    ASSUME(samplesToDo > 0);
+
+    const BiquadFilter filter{mFilter};
+    std::tie(z1, z2) = mFilter.getComponents();
+    for(size_t i{0u};i < samplesToDo;)
+    {
+        offset &= mask;
+        tap1 &= mask;
+        tap2 &= mask;
+
+        size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
+        do {
+            /* Feed the delay buffer's input first. */
+            delaybuf[offset] = samplesIn[0][i];
+
+            /* Get delayed output from the first and second taps. Use the
+             * second tap for feedback.
+             */
+            mTempBuffer[0][i] = delaybuf[tap1++];
+            mTempBuffer[1][i] = delaybuf[tap2++];
+            const float feedb{mTempBuffer[1][i++]};
+
+            /* Add feedback to the delay buffer with damping and attenuation. */
+            delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
+        } while(--td);
+    }
+    mFilter.setComponents(z1, z2);
+    mOffset = offset;
+
+    for(ALsizei c{0};c < 2;c++)
+        MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
+            samplesToDo, 0);
+}
+
+
+struct EchoStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new EchoState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *EchoStateFactory_getFactory()
+{
+    static EchoStateFactory EchoFactory{};
+    return &EchoFactory;
+}

+ 0 - 355
Engine/lib/openal-soft/Alc/effects/equalizer.c

@@ -1,355 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2013 by Mike Gorchak
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-/*  The document  "Effects Extension Guide.pdf"  says that low and high  *
- *  frequencies are cutoff frequencies. This is not fully correct, they  *
- *  are corner frequencies for low and high shelf filters. If they were  *
- *  just cutoff frequencies, there would be no need in cutoff frequency  *
- *  gains, which are present.  Documentation for  "Creative Proteus X2"  *
- *  software describes  4-band equalizer functionality in a much better  *
- *  way.  This equalizer seems  to be a predecessor  of  OpenAL  4-band  *
- *  equalizer.  With low and high  shelf filters  we are able to cutoff  *
- *  frequencies below and/or above corner frequencies using attenuation  *
- *  gains (below 1.0) and amplify all low and/or high frequencies using  *
- *  gains above 1.0.                                                     *
- *                                                                       *
- *     Low-shelf       Low Mid Band      High Mid Band     High-shelf    *
- *      corner            center             center          corner      *
- *     frequency        frequency          frequency       frequency     *
- *    50Hz..800Hz     200Hz..3000Hz      1000Hz..8000Hz  4000Hz..16000Hz *
- *                                                                       *
- *          |               |                  |               |         *
- *          |               |                  |               |         *
- *   B -----+            /--+--\            /--+--\            +-----    *
- *   O      |\          |   |   |          |   |   |          /|         *
- *   O      | \        -    |    -        -    |    -        / |         *
- *   S +    |  \      |     |     |      |     |     |      /  |         *
- *   T      |   |    |      |      |    |      |      |    |   |         *
- * ---------+---------------+------------------+---------------+-------- *
- *   C      |   |    |      |      |    |      |      |    |   |         *
- *   U -    |  /      |     |     |      |     |     |      \  |         *
- *   T      | /        -    |    -        -    |    -        \ |         *
- *   O      |/          |   |   |          |   |   |          \|         *
- *   F -----+            \--+--/            \--+--/            +-----    *
- *   F      |               |                  |               |         *
- *          |               |                  |               |         *
- *                                                                       *
- * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
- * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in   *
- * octaves for two mid bands.                                            *
- *                                                                       *
- * Implementation is based on the "Cookbook formulae for audio EQ biquad *
- * filter coefficients" by Robert Bristow-Johnson                        *
- * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt                   */
-
-
-typedef struct ALequalizerState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    struct {
-        /* Effect gains for each channel */
-        ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
-        ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
-
-        /* Effect parameters */
-        BiquadFilter filter[4];
-    } Chans[MAX_EFFECT_CHANNELS];
-
-    ALfloat SampleBuffer[MAX_EFFECT_CHANNELS][BUFFERSIZE];
-} ALequalizerState;
-
-static ALvoid ALequalizerState_Destruct(ALequalizerState *state);
-static ALboolean ALequalizerState_deviceUpdate(ALequalizerState *state, ALCdevice *device);
-static ALvoid ALequalizerState_update(ALequalizerState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALequalizerState_process(ALequalizerState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALequalizerState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALequalizerState);
-
-
-static void ALequalizerState_Construct(ALequalizerState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALequalizerState, ALeffectState, state);
-}
-
-static ALvoid ALequalizerState_Destruct(ALequalizerState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALequalizerState_deviceUpdate(ALequalizerState *state, ALCdevice *UNUSED(device))
-{
-    ALsizei i, j;
-
-    for(i = 0; i < MAX_EFFECT_CHANNELS;i++)
-    {
-        for(j = 0;j < 4;j++)
-            BiquadFilter_clear(&state->Chans[i].filter[j]);
-        for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
-            state->Chans[i].CurrentGains[j] = 0.0f;
-    }
-    return AL_TRUE;
-}
-
-static ALvoid ALequalizerState_update(ALequalizerState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALfloat frequency = (ALfloat)device->Frequency;
-    ALfloat gain, f0norm;
-    ALuint i;
-
-    STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
-    STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
-    for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
-        ComputeFirstOrderGains(&device->FOAOut, IdentityMatrixf.m[i],
-                               slot->Params.Gain, state->Chans[i].TargetGains);
-
-    /* Calculate coefficients for the each type of filter. Note that the shelf
-     * filters' gain is for the reference frequency, which is the centerpoint
-     * of the transition band.
-     */
-    gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
-    f0norm = props->Equalizer.LowCutoff/frequency;
-    BiquadFilter_setParams(&state->Chans[0].filter[0], BiquadType_LowShelf,
-        gain, f0norm, calc_rcpQ_from_slope(gain, 0.75f)
-    );
-
-    gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
-    f0norm = props->Equalizer.Mid1Center/frequency;
-    BiquadFilter_setParams(&state->Chans[0].filter[1], BiquadType_Peaking,
-        gain, f0norm, calc_rcpQ_from_bandwidth(
-            f0norm, props->Equalizer.Mid1Width
-        )
-    );
-
-    gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
-    f0norm = props->Equalizer.Mid2Center/frequency;
-    BiquadFilter_setParams(&state->Chans[0].filter[2], BiquadType_Peaking,
-        gain, f0norm, calc_rcpQ_from_bandwidth(
-            f0norm, props->Equalizer.Mid2Width
-        )
-    );
-
-    gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
-    f0norm = props->Equalizer.HighCutoff/frequency;
-    BiquadFilter_setParams(&state->Chans[0].filter[3], BiquadType_HighShelf,
-        gain, f0norm, calc_rcpQ_from_slope(gain, 0.75f)
-    );
-
-    /* Copy the filter coefficients for the other input channels. */
-    for(i = 1;i < MAX_EFFECT_CHANNELS;i++)
-    {
-        BiquadFilter_copyParams(&state->Chans[i].filter[0], &state->Chans[0].filter[0]);
-        BiquadFilter_copyParams(&state->Chans[i].filter[1], &state->Chans[0].filter[1]);
-        BiquadFilter_copyParams(&state->Chans[i].filter[2], &state->Chans[0].filter[2]);
-        BiquadFilter_copyParams(&state->Chans[i].filter[3], &state->Chans[0].filter[3]);
-    }
-}
-
-static ALvoid ALequalizerState_process(ALequalizerState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    ALfloat (*restrict temps)[BUFFERSIZE] = state->SampleBuffer;
-    ALsizei c;
-
-    for(c = 0;c < MAX_EFFECT_CHANNELS;c++)
-    {
-        BiquadFilter_process(&state->Chans[c].filter[0], temps[0], SamplesIn[c], SamplesToDo);
-        BiquadFilter_process(&state->Chans[c].filter[1], temps[1], temps[0], SamplesToDo);
-        BiquadFilter_process(&state->Chans[c].filter[2], temps[2], temps[1], SamplesToDo);
-        BiquadFilter_process(&state->Chans[c].filter[3], temps[3], temps[2], SamplesToDo);
-
-        MixSamples(temps[3], NumChannels, SamplesOut,
-            state->Chans[c].CurrentGains, state->Chans[c].TargetGains,
-            SamplesToDo, 0, SamplesToDo
-        );
-    }
-}
-
-
-typedef struct EqualizerStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} EqualizerStateFactory;
-
-ALeffectState *EqualizerStateFactory_create(EqualizerStateFactory *UNUSED(factory))
-{
-    ALequalizerState *state;
-
-    NEW_OBJ0(state, ALequalizerState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(EqualizerStateFactory);
-
-EffectStateFactory *EqualizerStateFactory_getFactory(void)
-{
-    static EqualizerStateFactory EqualizerFactory = { { GET_VTABLE2(EqualizerStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &EqualizerFactory);
-}
-
-
-void ALequalizer_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
-void ALequalizer_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
-void ALequalizer_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_EQUALIZER_LOW_GAIN:
-            if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
-            props->Equalizer.LowGain = val;
-            break;
-
-        case AL_EQUALIZER_LOW_CUTOFF:
-            if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
-            props->Equalizer.LowCutoff = val;
-            break;
-
-        case AL_EQUALIZER_MID1_GAIN:
-            if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
-            props->Equalizer.Mid1Gain = val;
-            break;
-
-        case AL_EQUALIZER_MID1_CENTER:
-            if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
-            props->Equalizer.Mid1Center = val;
-            break;
-
-        case AL_EQUALIZER_MID1_WIDTH:
-            if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
-            props->Equalizer.Mid1Width = val;
-            break;
-
-        case AL_EQUALIZER_MID2_GAIN:
-            if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
-            props->Equalizer.Mid2Gain = val;
-            break;
-
-        case AL_EQUALIZER_MID2_CENTER:
-            if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
-            props->Equalizer.Mid2Center = val;
-            break;
-
-        case AL_EQUALIZER_MID2_WIDTH:
-            if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
-            props->Equalizer.Mid2Width = val;
-            break;
-
-        case AL_EQUALIZER_HIGH_GAIN:
-            if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
-            props->Equalizer.HighGain = val;
-            break;
-
-        case AL_EQUALIZER_HIGH_CUTOFF:
-            if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
-            props->Equalizer.HighCutoff = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
-    }
-}
-void ALequalizer_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALequalizer_setParamf(effect, context, param, vals[0]); }
-
-void ALequalizer_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
-void ALequalizer_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
-void ALequalizer_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_EQUALIZER_LOW_GAIN:
-            *val = props->Equalizer.LowGain;
-            break;
-
-        case AL_EQUALIZER_LOW_CUTOFF:
-            *val = props->Equalizer.LowCutoff;
-            break;
-
-        case AL_EQUALIZER_MID1_GAIN:
-            *val = props->Equalizer.Mid1Gain;
-            break;
-
-        case AL_EQUALIZER_MID1_CENTER:
-            *val = props->Equalizer.Mid1Center;
-            break;
-
-        case AL_EQUALIZER_MID1_WIDTH:
-            *val = props->Equalizer.Mid1Width;
-            break;
-
-        case AL_EQUALIZER_MID2_GAIN:
-            *val = props->Equalizer.Mid2Gain;
-            break;
-
-        case AL_EQUALIZER_MID2_CENTER:
-            *val = props->Equalizer.Mid2Center;
-            break;
-
-        case AL_EQUALIZER_MID2_WIDTH:
-            *val = props->Equalizer.Mid2Width;
-            break;
-
-        case AL_EQUALIZER_HIGH_GAIN:
-            *val = props->Equalizer.HighGain;
-            break;
-
-        case AL_EQUALIZER_HIGH_CUTOFF:
-            *val = props->Equalizer.HighCutoff;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
-    }
-}
-void ALequalizer_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALequalizer_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALequalizer);

+ 184 - 0
Engine/lib/openal-soft/Alc/effects/equalizer.cpp

@@ -0,0 +1,184 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2013 by Mike Gorchak
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "core/filters/biquad.h"
+#include "effectslot.h"
+#include "vecmat.h"
+
+
+namespace {
+
+/*  The document  "Effects Extension Guide.pdf"  says that low and high  *
+ *  frequencies are cutoff frequencies. This is not fully correct, they  *
+ *  are corner frequencies for low and high shelf filters. If they were  *
+ *  just cutoff frequencies, there would be no need in cutoff frequency  *
+ *  gains, which are present.  Documentation for  "Creative Proteus X2"  *
+ *  software describes  4-band equalizer functionality in a much better  *
+ *  way.  This equalizer seems  to be a predecessor  of  OpenAL  4-band  *
+ *  equalizer.  With low and high  shelf filters  we are able to cutoff  *
+ *  frequencies below and/or above corner frequencies using attenuation  *
+ *  gains (below 1.0) and amplify all low and/or high frequencies using  *
+ *  gains above 1.0.                                                     *
+ *                                                                       *
+ *     Low-shelf       Low Mid Band      High Mid Band     High-shelf    *
+ *      corner            center             center          corner      *
+ *     frequency        frequency          frequency       frequency     *
+ *    50Hz..800Hz     200Hz..3000Hz      1000Hz..8000Hz  4000Hz..16000Hz *
+ *                                                                       *
+ *          |               |                  |               |         *
+ *          |               |                  |               |         *
+ *   B -----+            /--+--\            /--+--\            +-----    *
+ *   O      |\          |   |   |          |   |   |          /|         *
+ *   O      | \        -    |    -        -    |    -        / |         *
+ *   S +    |  \      |     |     |      |     |     |      /  |         *
+ *   T      |   |    |      |      |    |      |      |    |   |         *
+ * ---------+---------------+------------------+---------------+-------- *
+ *   C      |   |    |      |      |    |      |      |    |   |         *
+ *   U -    |  /      |     |     |      |     |     |      \  |         *
+ *   T      | /        -    |    -        -    |    -        \ |         *
+ *   O      |/          |   |   |          |   |   |          \|         *
+ *   F -----+            \--+--/            \--+--/            +-----    *
+ *   F      |               |                  |               |         *
+ *          |               |                  |               |         *
+ *                                                                       *
+ * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
+ * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in   *
+ * octaves for two mid bands.                                            *
+ *                                                                       *
+ * Implementation is based on the "Cookbook formulae for audio EQ biquad *
+ * filter coefficients" by Robert Bristow-Johnson                        *
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt                   */
+
+
+struct EqualizerState final : public EffectState {
+    struct {
+        /* Effect parameters */
+        BiquadFilter filter[4];
+
+        /* Effect gains for each channel */
+        float CurrentGains[MAX_OUTPUT_CHANNELS]{};
+        float TargetGains[MAX_OUTPUT_CHANNELS]{};
+    } mChans[MaxAmbiChannels];
+
+    FloatBufferLine mSampleBuffer{};
+
+
+    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
+    void update(const ALCcontext *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;
+
+    DEF_NEWDEL(EqualizerState)
+};
+
+void EqualizerState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    for(auto &e : mChans)
+    {
+        std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear));
+        std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+    }
+}
+
+void EqualizerState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+    auto frequency = static_cast<float>(device->Frequency);
+    float gain, f0norm;
+
+    /* Calculate coefficients for the each type of filter. Note that the shelf
+     * and peaking filters' gain is for the centerpoint of the transition band,
+     * while the effect property gains are for the shelf/peak itself. So the
+     * property gains need their dB halved (sqrt of linear gain) for the
+     * shelf/peak to reach the provided gain.
+     */
+    gain = std::sqrt(props->Equalizer.LowGain);
+    f0norm = props->Equalizer.LowCutoff / frequency;
+    mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
+
+    gain = std::sqrt(props->Equalizer.Mid1Gain);
+    f0norm = props->Equalizer.Mid1Center / frequency;
+    mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
+        props->Equalizer.Mid1Width);
+
+    gain = std::sqrt(props->Equalizer.Mid2Gain);
+    f0norm = props->Equalizer.Mid2Center / frequency;
+    mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
+        props->Equalizer.Mid2Width);
+
+    gain = std::sqrt(props->Equalizer.HighGain);
+    f0norm = props->Equalizer.HighCutoff / frequency;
+    mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
+
+    /* Copy the filter coefficients for the other input channels. */
+    for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
+    {
+        mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
+        mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
+        mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
+        mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
+    }
+
+    mOutTarget = target.Main->Buffer;
+    auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
+    { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
+    SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
+}
+
+void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    const al::span<float> buffer{mSampleBuffer.data(), samplesToDo};
+    auto chan = std::addressof(mChans[0]);
+    for(const auto &input : samplesIn)
+    {
+        const al::span<const float> inbuf{input.data(), samplesToDo};
+        DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin());
+        DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin());
+
+        MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u);
+        ++chan;
+    }
+}
+
+
+struct EqualizerStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new EqualizerState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *EqualizerStateFactory_getFactory()
+{
+    static EqualizerStateFactory EqualizerFactory{};
+    return &EqualizerFactory;
+}

+ 232 - 0
Engine/lib/openal-soft/Alc/effects/fshifter.cpp

@@ -0,0 +1,232 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2018 by Raul Herraiz.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <array>
+#include <complex>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcomplex.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "effectslot.h"
+#include "math_defs.h"
+
+
+namespace {
+
+using complex_d = std::complex<double>;
+
+#define HIL_SIZE 1024
+#define OVERSAMP (1<<2)
+
+#define HIL_STEP     (HIL_SIZE / OVERSAMP)
+#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
+
+/* Define a Hann window, used to filter the HIL input and output. */
+std::array<double,HIL_SIZE> InitHannWindow()
+{
+    std::array<double,HIL_SIZE> ret;
+    /* 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}};
+        const double val{std::sin(static_cast<double>(i+1) * scale)};
+        ret[i] = ret[HIL_SIZE-1-i] = val * val;
+    }
+    return ret;
+}
+alignas(16) const std::array<double,HIL_SIZE> HannWindow = InitHannWindow();
+
+
+struct FshifterState final : public EffectState {
+    /* Effect parameters */
+    size_t mCount{};
+    uint mPhaseStep[2]{};
+    uint mPhase[2]{};
+    double mSign[2]{};
+
+    /* Effects buffers */
+    double mInFIFO[HIL_SIZE]{};
+    complex_d mOutFIFO[HIL_STEP]{};
+    complex_d mOutputAccum[HIL_SIZE]{};
+    complex_d mAnalytic[HIL_SIZE]{};
+    complex_d mOutdata[BufferLineSize]{};
+
+    alignas(16) float mBufferOut[BufferLineSize]{};
+
+    /* Effect gains for each output channel */
+    struct {
+        float Current[MAX_OUTPUT_CHANNELS]{};
+        float Target[MAX_OUTPUT_CHANNELS]{};
+    } mGains[2];
+
+
+    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
+    void update(const ALCcontext *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;
+
+    DEF_NEWDEL(FshifterState)
+};
+
+void FshifterState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    /* (Re-)initializing parameters and clear the buffers. */
+    mCount = FIFO_LATENCY;
+
+    std::fill(std::begin(mPhaseStep),   std::end(mPhaseStep),   0u);
+    std::fill(std::begin(mPhase),       std::end(mPhase),       0u);
+    std::fill(std::begin(mSign),        std::end(mSign),        1.0);
+    std::fill(std::begin(mInFIFO),      std::end(mInFIFO),      0.0);
+    std::fill(std::begin(mOutFIFO),     std::end(mOutFIFO),     complex_d{});
+    std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
+    std::fill(std::begin(mAnalytic),    std::end(mAnalytic),    complex_d{});
+
+    for(auto &gain : mGains)
+    {
+        std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
+        std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
+    }
+}
+
+void FshifterState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+
+    const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)};
+    mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne);
+
+    switch(props->Fshifter.LeftDirection)
+    {
+    case FShifterDirection::Down:
+        mSign[0] = -1.0;
+        break;
+    case FShifterDirection::Up:
+        mSign[0] = 1.0;
+        break;
+    case FShifterDirection::Off:
+        mPhase[0]     = 0;
+        mPhaseStep[0] = 0;
+        break;
+    }
+
+    switch(props->Fshifter.RightDirection)
+    {
+    case FShifterDirection::Down:
+        mSign[1] = -1.0;
+        break;
+    case FShifterDirection::Up:
+        mSign[1] = 1.0;
+        break;
+    case FShifterDirection::Off:
+        mPhase[1]     = 0;
+        mPhaseStep[1] = 0;
+        break;
+    }
+
+    const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
+    const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
+
+    mOutTarget = target.Main->Buffer;
+    ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target);
+    ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target);
+}
+
+void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        size_t todo{minz(HIL_SIZE-mCount, samplesToDo-base)};
+
+        /* Fill FIFO buffer with samples data */
+        size_t count{mCount};
+        do {
+            mInFIFO[count] = samplesIn[0][base];
+            mOutdata[base] = mOutFIFO[count-FIFO_LATENCY];
+            ++base; ++count;
+        } while(--todo);
+        mCount = count;
+
+        /* Check whether FIFO buffer is filled */
+        if(mCount < HIL_SIZE) break;
+        mCount = FIFO_LATENCY;
+
+        /* Real signal windowing and store in Analytic buffer */
+        for(size_t k{0};k < HIL_SIZE;k++)
+            mAnalytic[k] = mInFIFO[k]*HannWindow[k];
+
+        /* Processing signal by Discrete Hilbert Transform (analytical signal). */
+        complex_hilbert(mAnalytic);
+
+        /* Windowing and add to output accumulator */
+        for(size_t k{0};k < HIL_SIZE;k++)
+            mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
+
+        /* Shift accumulator, input & output FIFO */
+        std::copy_n(mOutputAccum, HIL_STEP, mOutFIFO);
+        auto accum_iter = std::copy(std::begin(mOutputAccum)+HIL_STEP, std::end(mOutputAccum),
+            std::begin(mOutputAccum));
+        std::fill(accum_iter, std::end(mOutputAccum), complex_d{});
+        std::copy(std::begin(mInFIFO)+HIL_STEP, std::end(mInFIFO), std::begin(mInFIFO));
+    }
+
+    /* Process frequency shifter using the analytic signal obtained. */
+    float *RESTRICT BufferOut{mBufferOut};
+    for(int c{0};c < 2;++c)
+    {
+        const uint phase_step{mPhaseStep[c]};
+        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())};
+            BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
+                mOutdata[k].imag()*std::sin(phase)*mSign[c]);
+
+            phase_idx += phase_step;
+            phase_idx &= MixerFracMask;
+        }
+        mPhase[c] = phase_idx;
+
+        /* Now, mix the processed sound data to the output. */
+        MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
+            maxz(samplesToDo, 512), 0);
+    }
+}
+
+
+struct FshifterStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new FshifterState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *FshifterStateFactory_getFactory()
+{
+    static FshifterStateFactory FshifterFactory{};
+    return &FshifterFactory;
+}

+ 0 - 303
Engine/lib/openal-soft/Alc/effects/modulator.c

@@ -1,303 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2009 by Chris Robinson.
- * This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Library General Public
- *  License as published by the Free Software Foundation; either
- *  version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- *  License along with this library; if not, write to the
- *  Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
-
-#include "config.h"
-
-#include <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-
-#define MAX_UPDATE_SAMPLES 128
-
-typedef struct ALmodulatorState {
-    DERIVE_FROM_TYPE(ALeffectState);
-
-    void (*GetSamples)(ALfloat*, ALsizei, const ALsizei, ALsizei);
-
-    ALsizei index;
-    ALsizei step;
-
-    alignas(16) ALfloat ModSamples[MAX_UPDATE_SAMPLES];
-
-    struct {
-        BiquadFilter Filter;
-
-        ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
-        ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
-    } Chans[MAX_EFFECT_CHANNELS];
-} ALmodulatorState;
-
-static ALvoid ALmodulatorState_Destruct(ALmodulatorState *state);
-static ALboolean ALmodulatorState_deviceUpdate(ALmodulatorState *state, ALCdevice *device);
-static ALvoid ALmodulatorState_update(ALmodulatorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALmodulatorState_process(ALmodulatorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALmodulatorState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALmodulatorState);
-
-
-#define WAVEFORM_FRACBITS  24
-#define WAVEFORM_FRACONE   (1<<WAVEFORM_FRACBITS)
-#define WAVEFORM_FRACMASK  (WAVEFORM_FRACONE-1)
-
-static inline ALfloat Sin(ALsizei index)
-{
-    return sinf(index*(F_TAU/WAVEFORM_FRACONE) - F_PI)*0.5f + 0.5f;
-}
-
-static inline ALfloat Saw(ALsizei index)
-{
-    return (ALfloat)index / WAVEFORM_FRACONE;
-}
-
-static inline ALfloat Square(ALsizei index)
-{
-    return (ALfloat)((index >> (WAVEFORM_FRACBITS - 1)) & 1);
-}
-
-#define DECL_TEMPLATE(func)                                                   \
-static void Modulate##func(ALfloat *restrict dst, ALsizei index,              \
-                           const ALsizei step, ALsizei todo)                  \
-{                                                                             \
-    ALsizei i;                                                                \
-    for(i = 0;i < todo;i++)                                                   \
-    {                                                                         \
-        index += step;                                                        \
-        index &= WAVEFORM_FRACMASK;                                           \
-        dst[i] = func(index);                                                 \
-    }                                                                         \
-}
-
-DECL_TEMPLATE(Sin)
-DECL_TEMPLATE(Saw)
-DECL_TEMPLATE(Square)
-
-#undef DECL_TEMPLATE
-
-
-static void ALmodulatorState_Construct(ALmodulatorState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALmodulatorState, ALeffectState, state);
-
-    state->index = 0;
-    state->step = 1;
-}
-
-static ALvoid ALmodulatorState_Destruct(ALmodulatorState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALmodulatorState_deviceUpdate(ALmodulatorState *state, ALCdevice *UNUSED(device))
-{
-    ALsizei i, j;
-    for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
-    {
-        BiquadFilter_clear(&state->Chans[i].Filter);
-        for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
-            state->Chans[i].CurrentGains[j] = 0.0f;
-    }
-    return AL_TRUE;
-}
-
-static ALvoid ALmodulatorState_update(ALmodulatorState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
-    const ALCdevice *device = context->Device;
-    ALfloat cw, a;
-    ALsizei i;
-
-    if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
-        state->GetSamples = ModulateSin;
-    else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
-        state->GetSamples = ModulateSaw;
-    else /*if(Slot->Params.EffectProps.Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
-        state->GetSamples = ModulateSquare;
-
-    state->step = float2int(props->Modulator.Frequency*WAVEFORM_FRACONE/device->Frequency + 0.5f);
-    state->step = clampi(state->step, 1, WAVEFORM_FRACONE-1);
-
-    /* Custom filter coeffs, which match the old version instead of a low-shelf. */
-    cw = cosf(F_TAU * props->Modulator.HighPassCutoff / device->Frequency);
-    a = (2.0f-cw) - sqrtf(powf(2.0f-cw, 2.0f) - 1.0f);
-
-    state->Chans[0].Filter.b0 = a;
-    state->Chans[0].Filter.b1 = -a;
-    state->Chans[0].Filter.b2 = 0.0f;
-    state->Chans[0].Filter.a1 = -a;
-    state->Chans[0].Filter.a2 = 0.0f;
-    for(i = 1;i < MAX_EFFECT_CHANNELS;i++)
-        BiquadFilter_copyParams(&state->Chans[i].Filter, &state->Chans[0].Filter);
-
-    STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
-    STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
-    for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
-        ComputeFirstOrderGains(&device->FOAOut, IdentityMatrixf.m[i],
-                               slot->Params.Gain, state->Chans[i].TargetGains);
-}
-
-static ALvoid ALmodulatorState_process(ALmodulatorState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
-    ALfloat *restrict modsamples = ASSUME_ALIGNED(state->ModSamples, 16);
-    const ALsizei step = state->step;
-    ALsizei base;
-
-    for(base = 0;base < SamplesToDo;)
-    {
-        alignas(16) ALfloat temps[2][MAX_UPDATE_SAMPLES];
-        ALsizei td = mini(MAX_UPDATE_SAMPLES, SamplesToDo-base);
-        ALsizei c, i;
-
-        state->GetSamples(modsamples, state->index, step, td);
-        state->index += (step*td) & WAVEFORM_FRACMASK;
-        state->index &= WAVEFORM_FRACMASK;
-
-        for(c = 0;c < MAX_EFFECT_CHANNELS;c++)
-        {
-            BiquadFilter_process(&state->Chans[c].Filter, temps[0], &SamplesIn[c][base], td);
-            for(i = 0;i < td;i++)
-                temps[1][i] = temps[0][i] * modsamples[i];
-
-            MixSamples(temps[1], NumChannels, SamplesOut, state->Chans[c].CurrentGains,
-                       state->Chans[c].TargetGains, SamplesToDo-base, base, td);
-        }
-
-        base += td;
-    }
-}
-
-
-typedef struct ModulatorStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} ModulatorStateFactory;
-
-static ALeffectState *ModulatorStateFactory_create(ModulatorStateFactory *UNUSED(factory))
-{
-    ALmodulatorState *state;
-
-    NEW_OBJ0(state, ALmodulatorState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(ModulatorStateFactory);
-
-EffectStateFactory *ModulatorStateFactory_getFactory(void)
-{
-    static ModulatorStateFactory ModulatorFactory = { { GET_VTABLE2(ModulatorStateFactory, EffectStateFactory) } };
-
-    return STATIC_CAST(EffectStateFactory, &ModulatorFactory);
-}
-
-
-void ALmodulator_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_RING_MODULATOR_FREQUENCY:
-            if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range");
-            props->Modulator.Frequency = val;
-            break;
-
-        case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-            if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range");
-            props->Modulator.HighPassCutoff = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
-    }
-}
-void ALmodulator_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALmodulator_setParamf(effect, context, param, vals[0]); }
-void ALmodulator_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
-    ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_RING_MODULATOR_FREQUENCY:
-        case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-            ALmodulator_setParamf(effect, context, param, (ALfloat)val);
-            break;
-
-        case AL_RING_MODULATOR_WAVEFORM:
-            if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM))
-                SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform");
-            props->Modulator.Waveform = val;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
-    }
-}
-void ALmodulator_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALmodulator_setParami(effect, context, param, vals[0]); }
-
-void ALmodulator_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_RING_MODULATOR_FREQUENCY:
-            *val = (ALint)props->Modulator.Frequency;
-            break;
-        case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-            *val = (ALint)props->Modulator.HighPassCutoff;
-            break;
-        case AL_RING_MODULATOR_WAVEFORM:
-            *val = props->Modulator.Waveform;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
-    }
-}
-void ALmodulator_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALmodulator_getParami(effect, context, param, vals); }
-void ALmodulator_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-    const ALeffectProps *props = &effect->Props;
-    switch(param)
-    {
-        case AL_RING_MODULATOR_FREQUENCY:
-            *val = props->Modulator.Frequency;
-            break;
-        case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
-            *val = props->Modulator.HighPassCutoff;
-            break;
-
-        default:
-            alSetError(context, AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
-    }
-}
-void ALmodulator_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALmodulator_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALmodulator);

+ 173 - 0
Engine/lib/openal-soft/Alc/effects/modulator.cpp

@@ -0,0 +1,173 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2009 by Chris Robinson.
+ * This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Library General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ *  License along with this library; if not, write to the
+ *  Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include <cmath>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "core/filters/biquad.h"
+#include "effectslot.h"
+#include "vecmat.h"
+
+
+namespace {
+
+#define MAX_UPDATE_SAMPLES 128
+
+#define WAVEFORM_FRACBITS  24
+#define WAVEFORM_FRACONE   (1<<WAVEFORM_FRACBITS)
+#define WAVEFORM_FRACMASK  (WAVEFORM_FRACONE-1)
+
+inline float Sin(uint index)
+{
+    constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
+    return std::sin(static_cast<float>(index) * scale);
+}
+
+inline float Saw(uint index)
+{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
+
+inline float Square(uint index)
+{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
+
+inline float One(uint) { return 1.0f; }
+
+template<float (&func)(uint)>
+void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo)
+{
+    for(size_t i{0u};i < todo;i++)
+    {
+        index += step;
+        index &= WAVEFORM_FRACMASK;
+        dst[i] = func(index);
+    }
+}
+
+
+struct ModulatorState final : public EffectState {
+    void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
+
+    uint mIndex{0};
+    uint mStep{1};
+
+    struct {
+        BiquadFilter Filter;
+
+        float CurrentGains[MAX_OUTPUT_CHANNELS]{};
+        float TargetGains[MAX_OUTPUT_CHANNELS]{};
+    } mChans[MaxAmbiChannels];
+
+
+    void deviceUpdate(const ALCdevice *device, const Buffer &buffer) override;
+    void update(const ALCcontext *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;
+
+    DEF_NEWDEL(ModulatorState)
+};
+
+void ModulatorState::deviceUpdate(const ALCdevice*, const Buffer&)
+{
+    for(auto &e : mChans)
+    {
+        e.Filter.clear();
+        std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+    }
+}
+
+void ModulatorState::update(const ALCcontext *context, const EffectSlot *slot,
+    const EffectProps *props, const EffectTarget target)
+{
+    const ALCdevice *device{context->mDevice.get()};
+
+    const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)};
+    mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
+
+    if(mStep == 0)
+        mGetSamples = Modulate<One>;
+    else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid)
+        mGetSamples = Modulate<Sin>;
+    else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth)
+        mGetSamples = Modulate<Saw>;
+    else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/
+        mGetSamples = Modulate<Square>;
+
+    float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
+    f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
+    /* Bandwidth value is constant in octaves. */
+    mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
+    for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
+        mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
+
+    mOutTarget = target.Main->Buffer;
+    auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
+    { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
+    SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
+}
+
+void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+    for(size_t base{0u};base < samplesToDo;)
+    {
+        alignas(16) float modsamples[MAX_UPDATE_SAMPLES];
+        const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
+
+        mGetSamples(modsamples, mIndex, mStep, td);
+        mIndex += static_cast<uint>(mStep * td);
+        mIndex &= WAVEFORM_FRACMASK;
+
+        auto chandata = std::begin(mChans);
+        for(const auto &input : samplesIn)
+        {
+            alignas(16) float temps[MAX_UPDATE_SAMPLES];
+
+            chandata->Filter.process({&input[base], td}, temps);
+            for(size_t i{0u};i < td;i++)
+                temps[i] *= modsamples[i];
+
+            MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
+                samplesToDo-base, base);
+            ++chandata;
+        }
+
+        base += td;
+    }
+}
+
+
+struct ModulatorStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override
+    { return al::intrusive_ptr<EffectState>{new ModulatorState{}}; }
+};
+
+} // namespace
+
+EffectStateFactory *ModulatorStateFactory_getFactory()
+{
+    static ModulatorStateFactory ModulatorFactory{};
+    return &ModulatorFactory;
+}

+ 0 - 179
Engine/lib/openal-soft/Alc/effects/null.c

@@ -1,179 +0,0 @@
-#include "config.h"
-
-#include <stdlib.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-
-
-typedef struct ALnullState {
-    DERIVE_FROM_TYPE(ALeffectState);
-} ALnullState;
-
-/* Forward-declare "virtual" functions to define the vtable with. */
-static ALvoid ALnullState_Destruct(ALnullState *state);
-static ALboolean ALnullState_deviceUpdate(ALnullState *state, ALCdevice *device);
-static ALvoid ALnullState_update(ALnullState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALnullState_process(ALnullState *state, ALsizei samplesToDo, const ALfloat (*restrict samplesIn)[BUFFERSIZE], ALfloat (*restrict samplesOut)[BUFFERSIZE], ALsizei mumChannels);
-static void *ALnullState_New(size_t size);
-static void ALnullState_Delete(void *ptr);
-
-/* Define the ALeffectState vtable for this type. */
-DEFINE_ALEFFECTSTATE_VTABLE(ALnullState);
-
-
-/* This constructs the effect state. It's called when the object is first
- * created. Make sure to call the parent Construct function first, and set the
- * vtable!
- */
-static void ALnullState_Construct(ALnullState *state)
-{
-    ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
-    SET_VTABLE2(ALnullState, ALeffectState, state);
-}
-
-/* This destructs (not free!) the effect state. It's called only when the
- * effect slot is no longer used. Make sure to call the parent Destruct
- * function before returning!
- */
-static ALvoid ALnullState_Destruct(ALnullState *state)
-{
-    ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-/* This updates the device-dependant effect state. This is called on
- * initialization and any time the device parameters (eg. playback frequency,
- * format) have been changed.
- */
-static ALboolean ALnullState_deviceUpdate(ALnullState* UNUSED(state), ALCdevice* UNUSED(device))
-{
-    return AL_TRUE;
-}
-
-/* This updates the effect state. This is called any time the effect is
- * (re)loaded into a slot.
- */
-static ALvoid ALnullState_update(ALnullState* UNUSED(state), const ALCcontext* UNUSED(context), const ALeffectslot* UNUSED(slot), const ALeffectProps* UNUSED(props))
-{
-}
-
-/* This processes the effect state, for the given number of samples from the
- * input to the output buffer. The result should be added to the output buffer,
- * not replace it.
- */
-static ALvoid ALnullState_process(ALnullState* UNUSED(state), ALsizei UNUSED(samplesToDo), const ALfloatBUFFERSIZE*restrict UNUSED(samplesIn), ALfloatBUFFERSIZE*restrict UNUSED(samplesOut), ALsizei UNUSED(numChannels))
-{
-}
-
-/* This allocates memory to store the object, before it gets constructed.
- * DECLARE_DEFAULT_ALLOCATORS can be used to declare a default method.
- */
-static void *ALnullState_New(size_t size)
-{
-    return al_malloc(16, size);
-}
-
-/* This frees the memory used by the object, after it has been destructed.
- * DECLARE_DEFAULT_ALLOCATORS can be used to declare a default method.
- */
-static void ALnullState_Delete(void *ptr)
-{
-    al_free(ptr);
-}
-
-
-typedef struct NullStateFactory {
-    DERIVE_FROM_TYPE(EffectStateFactory);
-} NullStateFactory;
-
-/* Creates ALeffectState objects of the appropriate type. */
-ALeffectState *NullStateFactory_create(NullStateFactory *UNUSED(factory))
-{
-    ALnullState *state;
-
-    NEW_OBJ0(state, ALnullState)();
-    if(!state) return NULL;
-
-    return STATIC_CAST(ALeffectState, state);
-}
-
-/* Define the EffectStateFactory vtable for this type. */
-DEFINE_EFFECTSTATEFACTORY_VTABLE(NullStateFactory);
-
-EffectStateFactory *NullStateFactory_getFactory(void)
-{
-    static NullStateFactory NullFactory = { { GET_VTABLE2(NullStateFactory, EffectStateFactory) } };
-    return STATIC_CAST(EffectStateFactory, &NullFactory);
-}
-
-
-void ALnull_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
-    }
-}
-void ALnull_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint* UNUSED(vals))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer-vector property 0x%04x", param);
-    }
-}
-void ALnull_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
-    }
-}
-void ALnull_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat* UNUSED(vals))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect float-vector property 0x%04x", param);
-    }
-}
-
-void ALnull_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint* UNUSED(val))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
-    }
-}
-void ALnull_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint* UNUSED(vals))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect integer-vector property 0x%04x", param);
-    }
-}
-void ALnull_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat* UNUSED(val))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
-    }
-}
-void ALnull_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat* UNUSED(vals))
-{
-    switch(param)
-    {
-    default:
-        alSetError(context, AL_INVALID_ENUM, "Invalid null effect float-vector property 0x%04x", param);
-    }
-}
-
-DEFINE_ALEFFECT_VTABLE(ALnull);

+ 79 - 0
Engine/lib/openal-soft/Alc/effects/null.cpp

@@ -0,0 +1,79 @@
+
+#include "config.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "effects/base.h"
+#include "effectslot.h"
+
+
+namespace {
+
+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,
+        const EffectTarget target) override;
+    void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
+        const al::span<FloatBufferLine> samplesOut) override;
+
+    DEF_NEWDEL(NullState)
+};
+
+/* This constructs the effect state. It's called when the object is first
+ * created.
+ */
+NullState::NullState() = default;
+
+/* This destructs the effect state. It's called only when the effect instance
+ * is no longer used.
+ */
+NullState::~NullState() = default;
+
+/* This updates the device-dependant effect state. This is called on state
+ * initialization and any time the device parameters (e.g. playback frequency,
+ * format) have been changed. Will always be followed by a call to the update
+ * method, if successful.
+ */
+void NullState::deviceUpdate(const ALCdevice* /*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*/,
+    const EffectProps* /*props*/, const EffectTarget /*target*/)
+{
+}
+
+/* This processes the effect state, for the given number of samples from the
+ * input to the output buffer. The result should be added to the output buffer,
+ * not replace it.
+ */
+void NullState::process(const size_t/*samplesToDo*/,
+    const al::span<const FloatBufferLine> /*samplesIn*/,
+    const al::span<FloatBufferLine> /*samplesOut*/)
+{
+}
+
+
+struct NullStateFactory final : public EffectStateFactory {
+    al::intrusive_ptr<EffectState> create() override;
+};
+
+/* Creates EffectState objects of the appropriate type. */
+al::intrusive_ptr<EffectState> NullStateFactory::create()
+{ return al::intrusive_ptr<EffectState>{new NullState{}}; }
+
+} // namespace
+
+EffectStateFactory *NullStateFactory_getFactory()
+{
+    static NullStateFactory NullFactory{};
+    return &NullFactory;
+}

Some files were not shown because too many files changed in this diff